diff --git a/.changeset/large-oranges-press.md b/.changeset/large-oranges-press.md
new file mode 100644
index 0000000000..b59520bf19
--- /dev/null
+++ b/.changeset/large-oranges-press.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-org': patch
+---
+
+Add permission check to catalog create and refresh button
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 12d66efedb..13beb5b7a4 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -56,6 +56,7 @@
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
+ "@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
@@ -83,7 +84,7 @@
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/dev-utils": "workspace:^",
- "@backstage/plugin-permission-react": "workspace:^",
+ "@backstage/plugin-permission-common": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0",
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
index 1679e2ddf5..e27de38913 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
@@ -33,6 +33,12 @@ import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import React from 'react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { permissionApiRef } from '@backstage/plugin-permission-react';
+import { AuthorizeResult } from '@backstage/plugin-permission-common';
+
+const mockAuthorize = jest.fn();
+
+const mockPermissionApi = { authorize: mockAuthorize };
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -87,6 +93,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -143,6 +150,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -198,6 +206,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -240,6 +249,7 @@ describe('', () => {
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -276,6 +286,10 @@ describe('', () => {
},
};
+ mockAuthorize.mockImplementation(async () => ({
+ result: AuthorizeResult.ALLOW,
+ }));
+
await renderInTestApp(
', () => {
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, mockPermissionApi],
]}
>
@@ -308,6 +323,55 @@ describe('', () => {
);
});
+ it('should not render refresh button if the permission is DENY', async () => {
+ const entity = {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://backstage.io/catalog-info.yaml',
+ },
+ name: 'software-deny',
+ },
+ spec: {
+ owner: 'guest',
+ type: 'service',
+ lifecycle: 'production',
+ },
+ };
+
+ mockAuthorize.mockImplementation(async () => ({
+ result: AuthorizeResult.DENY,
+ }));
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(
+ screen.queryByTitle('Schedule entity refresh'),
+ ).not.toBeInTheDocument();
+ });
+
it('should not render refresh button if the location is not an url or file', async () => {
const entity = {
apiVersion: 'v1',
@@ -330,6 +394,7 @@ describe('', () => {
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -384,6 +449,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -440,6 +506,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -493,6 +560,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -546,6 +614,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -592,6 +661,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -659,6 +729,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
@@ -707,6 +778,7 @@ describe('', () => {
),
],
[catalogApiRef, catalogApi],
+ [permissionApiRef, {}],
]}
>
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
index 34aa463c39..763f6fc094 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
@@ -60,6 +60,8 @@ import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { parseEntityRef } from '@backstage/catalog-model';
+import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha';
+import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha';
const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';
@@ -108,6 +110,9 @@ export function AboutCard(props: AboutCardProps) {
const errorApi = useApi(errorApiRef);
const viewTechdocLink = useRouteRef(viewTechDocRouteRef);
const templateRoute = useRouteRef(createFromTemplateRouteRef);
+ const { allowed: canRefresh } = useEntityPermission(
+ catalogEntityRefreshPermission,
+ );
const entitySourceLocation = getEntitySourceLocation(
entity,
@@ -215,7 +220,7 @@ export function AboutCard(props: AboutCardProps) {
title="About"
action={
<>
- {allowRefresh && (
+ {allowRefresh && canRefresh && (
{
const origReplaceState = window.history.replaceState;
@@ -168,6 +170,7 @@ describe('DefaultCatalogPage', () => {
[identityApiRef, identityApi],
[storageApiRef, storageApi],
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
+ [permissionApiRef, new MockPermissionApi()],
]}
>
{children}
diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
index 8dfc29f87f..779b347b48 100644
--- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx
@@ -45,6 +45,8 @@ import { catalogTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { CatalogTableColumnsFunc } from '../CatalogTable/types';
+import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
+import { usePermission } from '@backstage/plugin-permission-react';
/** @internal */
export type BaseCatalogPageProps = {
@@ -60,15 +62,20 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) {
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
const createComponentLink = useRouteRef(createComponentRouteRef);
const { t } = useTranslationRef(catalogTranslationRef);
+ const { allowed } = usePermission({
+ permission: catalogEntityCreatePermission,
+ });
return (
-
+ {allowed && (
+
+ )}
All your software catalog entities
diff --git a/plugins/org/package.json b/plugins/org/package.json
index 3e006fb958..6f46f44740 100644
--- a/plugins/org/package.json
+++ b/plugins/org/package.json
@@ -33,6 +33,7 @@
"@backstage/catalog-model": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
+ "@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.12.2",
diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx
index 76527362ff..33c6ee137a 100644
--- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx
+++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx
@@ -54,6 +54,8 @@ import EditIcon from '@material-ui/icons/Edit';
import EmailIcon from '@material-ui/icons/Email';
import GroupIcon from '@material-ui/icons/Group';
import { LinksGroup } from '../../Meta';
+import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha';
+import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha';
const CardTitle = (props: { title: string }) => (
@@ -70,6 +72,9 @@ export const GroupProfileCard = (props: {
const catalogApi = useApi(catalogApiRef);
const alertApi = useApi(alertApiRef);
const { entity: group } = useEntity();
+ const { allowed: canRefresh } = useEntityPermission(
+ catalogEntityRefreshPermission,
+ );
const refreshEntity = useCallback(async () => {
await catalogApi.refreshEntity(stringifyEntityRef(group));
@@ -127,7 +132,7 @@ export const GroupProfileCard = (props: {
variant={props.variant}
action={
<>
- {allowRefresh && (
+ {allowRefresh && canRefresh && (