docs: add permissions documentation for actions registry

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2026-03-12 10:01:42 +01:00
parent cc8348ef2e
commit d504478c1e
5 changed files with 72 additions and 4 deletions
+25
View File
@@ -3,3 +3,28 @@
---
Added optional `permission` field to `ActionsRegistryActionOptions`, allowing actions to declare a `BasicPermission` that controls visibility and access.
```typescript
import { createPermission } from '@backstage/plugin-permission-common';
const myPermission = createPermission({
name: 'myPlugin.myAction.use',
attributes: {},
});
actionsRegistry.register({
name: 'my-action',
title: 'My Action',
description: 'An action that requires permission',
permission: myPermission,
schema: {
input: z => z.object({ name: z.string() }),
output: z => z.object({ ok: z.boolean() }),
},
action: async ({ input }) => {
return { output: { ok: true } };
},
});
```
Actions without a `permission` field continue to work as before.
+1 -1
View File
@@ -2,4 +2,4 @@
'@backstage/backend-defaults': patch
---
Added permissions integration to the actions registry. Actions with a `permission` field are now checked against the permissions framework when listing and invoking. Denied actions are filtered from listings and return 404 on invocation.
Added permissions integration to the actions registry. Actions registered with a `permission` field are now checked against the permissions framework when listing and invoking. Denied actions are filtered from list results, and invoking a denied action returns a `404 Not Found` as if the action does not exist.
@@ -25,6 +25,7 @@ Each action registered with the service must conform to the `ActionsRegistryActi
### Optional Properties
- **`permission`:** A `BasicPermission` that controls visibility and access to the action through the permissions framework. See [Permissions](#permissions) below.
- **`attributes`:** Object containing behavioral flags:
- **`destructive`:** Boolean indicating if the action modifies or deletes data
- **`idempotent`:** Boolean indicating if running the action multiple times produces the same result
@@ -157,6 +158,41 @@ export const myPlugin = createBackendPlugin({
});
```
## Permissions
Actions can optionally declare a `permission` to control visibility and access through the Backstage permissions framework. When a permission is set, the action is only visible in listings and accessible by users who are authorized.
Actions that are denied by the permission policy are filtered from `list()` results and return a `404 Not Found` on `invoke()`, as if they don't exist.
### Adding a Permission to an Action
```typescript
import { createPermission } from '@backstage/plugin-permission-common';
// Define a permission for your action
const deleteEntityPermission = createPermission({
name: 'catalog.entity.delete',
attributes: { action: 'delete' },
});
actionsRegistry.register({
name: 'delete-entity',
title: 'Delete Entity',
description: 'Removes an entity from the catalog',
permission: deleteEntityPermission,
schema: {
input: z => z.object({ entityRef: z.string() }),
output: z => z.object({ deleted: z.boolean() }),
},
action: async ({ input }) => {
// action logic
return { output: { deleted: true } };
},
});
```
Actions without a `permission` field remain visible and accessible by all callers, preserving backwards compatibility.
## Best Practices
### Naming Conventions
@@ -65,6 +65,10 @@ backend:
- 'scaffolder.internal.*'
```
### Permissions
Actions registered with a `permission` field are automatically checked against the permissions framework. When listing actions, any actions denied by the active permission policy are filtered out of the results. When invoking a denied action, a `404 Not Found` error is returned. See the [Actions Registry Permissions](./actions-registry.md#permissions) documentation for how to configure permissions on actions.
## Using the Service
### Listing Available Actions
@@ -16,6 +16,7 @@
import {
AuthService,
BackstageCredentials,
HttpAuthService,
LoggerService,
PermissionsService,
@@ -32,6 +33,8 @@ import {
import { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
type ActionEntry = [string, ActionsRegistryActionOptions<any, any>];
export class DefaultActionsRegistryService implements ActionsRegistryService {
private actions: Map<string, ActionsRegistryActionOptions<any, any>> =
new Map();
@@ -196,9 +199,9 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
}
private async filterByPermissions(
entries: [string, ActionsRegistryActionOptions<any, any>][],
credentials: Parameters<PermissionsService['authorize']>[1]['credentials'],
): Promise<[string, ActionsRegistryActionOptions<any, any>][]> {
entries: ActionEntry[],
credentials: BackstageCredentials,
): Promise<ActionEntry[]> {
const permissionedEntries = entries.filter(
([_, action]) => action.permission,
);