Skip to content

Commit

Permalink
feat(orchestrator): disable buttons based on permissions (#1818)
Browse files Browse the repository at this point in the history
* feat(orchestrator): disable buttons based on permissions

Signed-off-by: Roy Golan <rgolan@redhat.com>
Signed-off-by: gabriel-farache <gfarache@redhat.com>

* Add tooltip when not authorized

* Fix errors

Signed-off-by: gabriel-farache <gfarache@redhat.com>

---------

Signed-off-by: Roy Golan <rgolan@redhat.com>
Signed-off-by: gabriel-farache <gfarache@redhat.com>
Co-authored-by: Roy Golan <rgolan@redhat.com>
  • Loading branch information
gabriel-farache and rgolangh committed Jun 26, 2024
1 parent 213171d commit 36504b0
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 51 deletions.
9 changes: 5 additions & 4 deletions plugins/orchestrator-backend/dist-dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"scripts": {},
"dependencies": {
"@janus-idp/backstage-plugin-audit-log-node": "^1.2.0",
"@janus-idp/backstage-plugin-rbac-common": "^1.6.0",
"@urql/core": "^4.1.4",
"ajv-formats": "^2.1.1",
"cloudevents": "^8.0.0",
Expand All @@ -72,21 +73,21 @@
"@backstage/backend-app-api": "^0.7.5",
"@backstage/backend-common": "^0.22.0",
"@backstage/backend-dynamic-feature-service": "^0.2.10",
"@backstage/errors": "^1.2.4",
"@backstage/backend-plugin-api": "^0.6.18",
"@backstage/backend-tasks": "^0.5.23",
"@backstage/catalog-client": "^1.6.5",
"@backstage/config": "^1.2.0",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/errors": "^1.2.4",
"@backstage/integration": "^1.11.0",
"@backstage/plugin-auth-node": "^0.4.13",
"@backstage/plugin-catalog-node": "^1.12.0",
"@backstage/plugin-events-backend": "^0.3.5",
"@backstage/plugin-events-node": "^0.3.4",
"@backstage/plugin-scaffolder-backend": "^1.22.8",
"@backstage/plugin-scaffolder-node": "^0.4.4",
"@backstage/plugin-permission-common": "^0.7.13",
"@backstage/plugin-permission-node": "^0.7.29",
"@backstage/plugin-auth-node": "^0.4.13",
"@backstage/plugin-scaffolder-backend": "^1.22.8",
"@backstage/plugin-scaffolder-node": "^0.4.4",
"@backstage/types": "^1.1.1"
},
"overrides": {
Expand Down
22 changes: 15 additions & 7 deletions plugins/orchestrator-backend/dist-dynamic/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -790,9 +790,9 @@
tslib "^2.6.2"

"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.9.1":
version "1.16.0"
resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.0.tgz#631172e2fe0346cf4410d1c8e01ad98d849738e2"
integrity sha512-CeuTvsXxCUmEuxH5g/aceuSl6w2EugvNHKAtKKVdiX915EjJJxAwfzNNWZreNnbxHZ2fi0zaM6wwS23x2JVqSQ==
version "1.16.1"
resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.1.tgz#069caf02ca283027dd0a3cf37817e674ebf130c8"
integrity sha512-ExPSbgjwCoht6kB7B4MeZoBAxcQSIl29r/bPeazZJx50ej4JJCByimLOrZoIsurISNyJQQHf30b3JfqC3Hb88A==
dependencies:
"@azure/abort-controller" "^2.0.0"
"@azure/core-auth" "^1.4.0"
Expand Down Expand Up @@ -1078,7 +1078,7 @@
zod "^3.22.4"
zod-to-json-schema "^3.21.4"

"@backstage/plugin-permission-common@^0.7.14":
"@backstage/plugin-permission-common@^0.7.13", "@backstage/plugin-permission-common@^0.7.14":
version "0.7.14"
resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-common/-/plugin-permission-common-0.7.14.tgz#ecb12877c412ff271124af54fca46ec06d9c812f"
integrity sha512-fHbxhX9ZoT8bTVuGycfTeU/6TE2yjZ6YNvm/2ko1bcxGnvYe1p5Ug5JW+iWjDZS+F6F152tWzhRcg05wQlPNKQ==
Expand Down Expand Up @@ -1181,6 +1181,14 @@
express "^4.19.2"
lodash "^4.17.21"

"@janus-idp/backstage-plugin-rbac-common@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@janus-idp/backstage-plugin-rbac-common/-/backstage-plugin-rbac-common-1.6.0.tgz#254f6b26daaba1faeb6672d1692c042dacab4db6"
integrity sha512-3qyeGkpVa7X2gS8LUmAHtgLyLTEWtfeGvZcLJUuliStW5KWppxFXZbhOlrlY4aZOFWFgVVngs09NO7EbwanqcA==
dependencies:
"@backstage/errors" "^1.2.4"
"@backstage/plugin-permission-common" "^0.7.13"

"@jridgewell/resolve-uri@^3.0.3":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
Expand Down Expand Up @@ -3726,9 +3734,9 @@ isarray@~1.0.0:
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==

isomorphic-git@^1.23.0:
version "1.26.2"
resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.26.2.tgz#424958f1a8aadf440fdab9b8cf65aeaf09d84b03"
integrity sha512-swKxfDm/GyaU5ojGwKtfs3EiHwlUf3iP1iIHPduaJHA5y1cIzMDFo3ogwdW6i00UmAjYQNKyF2BZgj/jz58/2A==
version "1.26.3"
resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.26.3.tgz#4cc7860ec915729e1daed7b51be9371da5da1574"
integrity sha512-NVicJz3RvUhllSSZnCVtTOqWhxlMln5OD3mh00334wCYhiMDuMiYsNJqs3sCHL7oXiv1tP93jMaQTdN7DkPCOg==
dependencies:
async-lock "^1.4.1"
clean-git-ref "^2.0.1"
Expand Down
9 changes: 5 additions & 4 deletions plugins/orchestrator-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,25 @@
"@backstage/backend-app-api": "^0.7.5",
"@backstage/backend-common": "^0.22.0",
"@backstage/backend-dynamic-feature-service": "^0.2.10",
"@backstage/errors": "^1.2.4",
"@backstage/backend-plugin-api": "^0.6.18",
"@backstage/backend-tasks": "^0.5.23",
"@backstage/catalog-client": "^1.6.5",
"@backstage/config": "^1.2.0",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/errors": "^1.2.4",
"@backstage/integration": "^1.11.0",
"@backstage/plugin-auth-node": "^0.4.13",
"@backstage/plugin-catalog-node": "^1.12.0",
"@backstage/plugin-events-backend": "^0.3.5",
"@backstage/plugin-events-node": "^0.3.4",
"@backstage/plugin-scaffolder-backend": "^1.22.8",
"@backstage/plugin-scaffolder-node": "^0.4.4",
"@backstage/plugin-permission-common": "^0.7.13",
"@backstage/plugin-permission-node": "^0.7.29",
"@backstage/plugin-auth-node": "^0.4.13",
"@backstage/plugin-scaffolder-backend": "^1.22.8",
"@backstage/plugin-scaffolder-node": "^0.4.4",
"@backstage/types": "^1.1.1",
"@janus-idp/backstage-plugin-audit-log-node": "1.2.0",
"@janus-idp/backstage-plugin-orchestrator-common": "1.9.0",
"@janus-idp/backstage-plugin-rbac-common": "^1.6.0",
"@urql/core": "^4.1.4",
"ajv-formats": "^2.1.1",
"cloudevents": "^8.0.0",
Expand Down
6 changes: 1 addition & 5 deletions plugins/orchestrator-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { NotAllowedError } from '@backstage/errors';
import {
AuthorizeResult,
BasicPermission,
Expand Down Expand Up @@ -43,6 +42,7 @@ import {
QUERY_PARAM_INSTANCE_ID,
WorkflowInputSchemaResponse,
} from '@janus-idp/backstage-plugin-orchestrator-common';
import { UnauthorizedError } from '@janus-idp/backstage-plugin-rbac-common';

import * as pkg from '../../package.json';
import { RouterArgs } from '../routerWrapper';
Expand Down Expand Up @@ -86,10 +86,6 @@ const authorize = async (
return decision;
};

declare class UnauthorizedError extends NotAllowedError {
message: 'Unauthorized';
}

export async function createBackendRouter(
args: RouterArgs,
): Promise<express.Router> {
Expand Down
14 changes: 14 additions & 0 deletions plugins/orchestrator/docs/Permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ g, user:default/guest, role:default/workflowViewer
g, user:default/myOrgUser, role:default/workflowAdmin
g, group:default/platformAdmins, role:default/worflowAdmin
```

See https://casbin.org/docs/rbac for more information about casbin rules

## Enable permissions

To enable permissions, you need to add the following in the [app-config file](../../../app-config.yaml):

```
permission:
enabled: true
rbac:
policies-csv-file: <absolute path to the policy file>
policyFileReload: true
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import {
useRouteRef,
useRouteRefParams,
} from '@backstage/core-plugin-api';
import { usePermission } from '@backstage/plugin-permission-react';

import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Skeleton from '@mui/material/Skeleton';
import Tooltip from '@mui/material/Tooltip';

import { orchestratorWorkflowExecutePermission } from '@janus-idp/backstage-plugin-orchestrator-common';

import { orchestratorApiRef } from '../../api';
import {
Expand All @@ -25,6 +29,9 @@ import WorkflowDefinitionDetailsCard from './WorkflowDefinitionDetailsCard';
export const WorkflowDefinitionViewerPage = () => {
const { workflowId, format } = useRouteRefParams(workflowDefinitionsRouteRef);
const orchestratorApi = useApi(orchestratorApiRef);
const { loading: loadingPermission, allowed: canRun } = usePermission({
permission: orchestratorWorkflowExecutePermission,
});
const {
value: workflowOverview,
loading,
Expand Down Expand Up @@ -58,16 +65,22 @@ export const WorkflowDefinitionViewerPage = () => {
)}
<Grid container item justifyContent="flex-end" spacing={1}>
<Grid item>
{loading ? (
{loading || loadingPermission ? (
<Skeleton variant="text" width="5rem" />
) : (
<Button
variant="contained"
color="primary"
onClick={handleExecute}
<Tooltip
title="user not authorized to execute workflow"
disableHoverListener={canRun}
>
Run
</Button>
<Button
variant="contained"
color="primary"
onClick={handleExecute}
disabled={!canRun}
>
Run
</Button>
</Tooltip>
)}
</Grid>
</Grid>
Expand Down
72 changes: 49 additions & 23 deletions plugins/orchestrator/src/components/WorkflowInstancePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import {
useRouteRef,
useRouteRefParams,
} from '@backstage/core-plugin-api';
import { usePermission } from '@backstage/plugin-permission-react';

import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Tooltip from '@mui/material/Tooltip';

import {
AssessedProcessInstance,
orchestratorWorkflowExecutePermission,
orchestratorWorkflowInstanceAbortPermission,
QUERY_PARAM_ASSESSMENT_INSTANCE_ID,
QUERY_PARAM_INSTANCE_ID,
QUERY_PARAM_INSTANCE_STATE,
Expand Down Expand Up @@ -88,6 +92,13 @@ export const WorkflowInstancePage = ({
const [isAbortAlertDialogOpen, setIsAbortAlertDialogOpen] = useState(false);
const [abortWorkflowInstanceErrorMsg, setAbortWorkflowInstanceErrorMsg] =
useState('');
const permittedToExecute = usePermission({
permission: orchestratorWorkflowExecutePermission,
});

const permittedToAbort = usePermission({
permission: orchestratorWorkflowInstanceAbortPermission,
});

const fetchInstance = React.useCallback(async () => {
if (!instanceId && !queryInstanceId) {
Expand Down Expand Up @@ -201,39 +212,54 @@ export const WorkflowInstancePage = ({
{!canRerun && (
<>
<Grid item>
<Button
variant="contained"
color="primary"
disabled={!canAbort}
onClick={canAbort ? handleRerun : undefined}
<Tooltip
title="user not authorized to execute workflow"
disableHoverListener={permittedToExecute.allowed}
>
Retrigger
</Button>
<Button
variant="contained"
color="primary"
disabled={!permittedToExecute.allowed || !canRerun}
onClick={canRerun ? handleRerun : undefined}
>
Retrigger
</Button>
</Tooltip>
</Grid>
<Grid item>
<Button
variant="contained"
color="secondary"
disabled={!canAbort}
onClick={
canAbort ? toggleAbortConfirmationDialog : undefined
}
<Tooltip
title="user not authorized to abort workflow"
disableHoverListener={permittedToAbort.allowed}
>
Abort
</Button>
<Button
variant="contained"
color="secondary"
disabled={!permittedToAbort.allowed || !canAbort}
onClick={
canAbort ? toggleAbortConfirmationDialog : undefined
}
>
Abort
</Button>
</Tooltip>
</Grid>
</>
)}
{!canAbort && (
<Grid item>
<Button
variant="contained"
color="primary"
disabled={!canRerun}
onClick={canRerun ? handleRerun : undefined}
<Tooltip
title="user not authorized to execute workflow"
disableHoverListener={permittedToExecute.allowed}
>
Rerun
</Button>
<Button
variant="contained"
color="primary"
disabled={!permittedToExecute.allowed || !canRerun}
onClick={canRerun ? handleRerun : undefined}
>
Rerun
</Button>
</Tooltip>
</Grid>
)}
</Grid>
Expand Down
8 changes: 7 additions & 1 deletion plugins/orchestrator/src/components/WorkflowsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { useNavigate } from 'react-router-dom';

import { Link, TableColumn, TableProps } from '@backstage/core-components';
import { useRouteRef } from '@backstage/core-plugin-api';
import { usePermission } from '@backstage/plugin-permission-react';

import Pageview from '@mui/icons-material/Pageview';
import PlayArrow from '@mui/icons-material/PlayArrow';

import {
capitalize,
orchestratorWorkflowExecutePermission,
ProcessInstanceStateValues,
WorkflowOverview,
} from '@janus-idp/backstage-plugin-orchestrator-common';
Expand All @@ -33,6 +35,9 @@ export const WorkflowsTable = ({ items }: WorkflowsTableProps) => {
const definitionLink = useRouteRef(workflowDefinitionsRouteRef);
const executeWorkflowLink = useRouteRef(executeWorkflowRouteRef);
const [data, setData] = useState<FormattedWorkflowOverview[]>([]);
const permittedToExecute = usePermission({
permission: orchestratorWorkflowExecutePermission,
});

const initialState = useMemo(
() => items.map(WorkflowOverviewFormatter.format),
Expand Down Expand Up @@ -64,6 +69,7 @@ export const WorkflowsTable = ({ items }: WorkflowsTableProps) => {
{
icon: () => <PlayArrow />,
tooltip: 'Execute',
disabled: !permittedToExecute.allowed,
onClick: (_, rowData) =>
handleExecute(rowData as FormattedWorkflowOverview),
},
Expand All @@ -76,7 +82,7 @@ export const WorkflowsTable = ({ items }: WorkflowsTableProps) => {
];

return actionItems;
}, [handleExecute, handleView]);
}, [handleExecute, handleView, permittedToExecute]);

const columns = useMemo<TableColumn<FormattedWorkflowOverview>[]>(
() => [
Expand Down

0 comments on commit 36504b0

Please sign in to comment.