Merge pull request #28677 from backstage/rugvip/permission-refs
permissions: add resources refs and rule accessor
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-node': minor
|
||||
---
|
||||
|
||||
Added new `catalogEntityPermissionResourceRef` export via the `/alpha` sub-path.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-permission-node': patch
|
||||
---
|
||||
|
||||
Added a new `PermissionRuleset` type that encapsulates a lookup function for permission rules, which can be created by the new `PermissionsRegistryService` via the `getPermissionRuleset` method. The `createConditionTransformer` and `createConditionAuthorizer` functions have been adapted to receive these accessors as arguments, with their older counterparts being deprecated.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Added the new `getPermissionRuleset` method to `mockServices.permissionsRegistry`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Updated permission integration to use new permission resource ref.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-permission-node': patch
|
||||
---
|
||||
|
||||
Added a new `createPermissionResourceRef` utility that encapsulates the constants and types related to a permission resource types. The `createConditionExports` and `createPermissionRule` functions have also been adapted to accept these references as arguments, deprecating their older counterparts.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Updated `PermissionsRegistryService` to use `PermissionResourceRef`s and added the `getPermissionRuleset` method.
|
||||
@@ -24,14 +24,14 @@ yarn --cwd packages/backend add zod @backstage/catalog-model
|
||||
...
|
||||
|
||||
import type { Entity } from '@backstage/catalog-model';
|
||||
import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha';
|
||||
import { createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-backend/alpha';
|
||||
import { createConditionFactory, createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const isInSystemRule = createCatalogPermissionRule({
|
||||
export const isInSystemRule = createPermissionRule({
|
||||
name: 'IS_IN_SYSTEM',
|
||||
description: 'Checks if an entity is part of the system provided',
|
||||
resourceType: 'catalog-entity',
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
systemRef: z
|
||||
.string()
|
||||
@@ -64,22 +64,22 @@ Still in the `packages/backend/src/extensions/permissionsPolicyExtension.ts` fil
|
||||
```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts"
|
||||
...
|
||||
/* highlight-remove-next-line */
|
||||
import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-backend/alpha';
|
||||
/* highlight-add-next-line */
|
||||
import { catalogConditions, createCatalogConditionalDecision, createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha';
|
||||
import { catalogEntityPermissionResourceRef, createCatalogConditionalDecision, catalogConditions } from '@backstage/plugin-catalog-backend/alpha';
|
||||
/* highlight-remove-next-line */
|
||||
import { createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
import { createConditionFactory, createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
/* highlight-add-next-line */
|
||||
import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node';
|
||||
import { createConditionFactory, createPermissionRule, PermissionPolicy, PolicyQuery, PolicyQueryUser } from '@backstage/plugin-permission-node';
|
||||
/* highlight-add-start */
|
||||
import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common';
|
||||
/* highlight-add-end */
|
||||
...
|
||||
|
||||
export const isInSystemRule = createCatalogPermissionRule({
|
||||
export const isInSystemRule = createPermissionRule({
|
||||
name: 'IS_IN_SYSTEM',
|
||||
description: 'Checks if an entity is part of the system provided',
|
||||
resourceType: 'catalog-entity',
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
systemRef: z
|
||||
.string()
|
||||
|
||||
@@ -130,16 +130,18 @@ import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
|
||||
import { z } from 'zod';
|
||||
import { Todo, TodoFilter } from './todos';
|
||||
|
||||
export const createTodoListPermissionRule = makeCreatePermissionRule<
|
||||
export const todoListPermissionResourceRef = createPermissionResourceRef<
|
||||
Todo,
|
||||
TodoFilter,
|
||||
typeof TODO_LIST_RESOURCE_TYPE
|
||||
>();
|
||||
TodoFilter
|
||||
>().with({
|
||||
pluginId: 'todolist',
|
||||
type: TODO_LIST_RESOURCE_TYPE,
|
||||
});
|
||||
|
||||
export const isOwner = createTodoListPermissionRule({
|
||||
export const isOwner = createPermissionRule({
|
||||
name: 'IS_OWNER',
|
||||
description: 'Should allow only if the todo belongs to the user',
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
resourceType: todoListPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
userId: z.string().describe('User ID to match on the resource'),
|
||||
}),
|
||||
@@ -157,11 +159,11 @@ export const isOwner = createTodoListPermissionRule({
|
||||
export const rules = { isOwner };
|
||||
```
|
||||
|
||||
`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query.
|
||||
The `todoListPermissionResourceRef` is a utility that encapsulates the types and constants related to the resource type. It ensures that the resource and query types are consistent across all rules created for this resource.
|
||||
|
||||
:::note Note
|
||||
|
||||
To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `extension point`.
|
||||
To support custom rules defined by Backstage integrators, you must export `todoListPermissionResourceRef` from the backend package, or a `*-node` package if you want to enable the creation of third-party modules.
|
||||
|
||||
:::
|
||||
|
||||
@@ -186,14 +188,12 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './service/router';
|
||||
import {
|
||||
/* highlight-add-next-line */
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
todoListCreatePermission,
|
||||
todoListUpdatePermission,
|
||||
} from '@internal/plugin-todo-list-common';
|
||||
/* highlight-add-start */
|
||||
import { getTodo } from './todos';
|
||||
import { rules } from './rules';
|
||||
import { todoListPermissionResourceRef, rules } from './rules';
|
||||
/* highlight-add-end */
|
||||
|
||||
// ...
|
||||
@@ -206,7 +206,7 @@ permissionsRegistry.addPermissions([
|
||||
/* highlight-remove-end */
|
||||
/* highlight-add-start */
|
||||
permissionsRegistry.addResourceType({
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
resourceRef: todoListPermissionResourceRef,
|
||||
permissions: [todoListCreatePermission, todoListUpdatePermission],
|
||||
rules: Object.values(rules),
|
||||
getResources: async resourceRefs => {
|
||||
@@ -225,11 +225,10 @@ Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add th
|
||||
```typescript title="plugins/todo-list-backend/src/conditionExports.ts"
|
||||
import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';
|
||||
import { createConditionExports } from '@backstage/plugin-permission-node';
|
||||
import { rules } from './service/rules';
|
||||
import { todoListPermissionResourceRef, rules } from './service/rules';
|
||||
|
||||
const { conditions, createConditionalDecision } = createConditionExports({
|
||||
pluginId: 'todolist',
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
resourceRef: todoListPermissionResourceRef,
|
||||
rules,
|
||||
});
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ import {
|
||||
// ...
|
||||
|
||||
permissionsRegistry.addResourceType({
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
resourceRef: todoListPermissionResourceRef,
|
||||
/* highlight-remove-next-line */
|
||||
permissions: [todoListCreatePermission, todoListUpdatePermission],
|
||||
/* highlight-add-next-line */
|
||||
@@ -129,17 +129,22 @@ import {
|
||||
import { add, getAll, getTodo, update } from './todos';
|
||||
/* highlight-add-next-line */
|
||||
import { add, getAll, getTodo, TodoFilter, update } from './todos';
|
||||
/* highlight-add-next-line */
|
||||
import { todoListPermissionResourceRef } from './rules';
|
||||
import {
|
||||
todoListCreatePermission,
|
||||
todoListUpdatePermission,
|
||||
/* highlight-add-next-line */
|
||||
todoListReadPermission,
|
||||
} from './permissions';
|
||||
} from '@internal/plugin-todo-list-common';
|
||||
|
||||
// ...
|
||||
|
||||
/* highlight-add-next-line */
|
||||
const transformConditions: ConditionTransformer<TodoFilter> = createConditionTransformer(Object.values(rules));
|
||||
/* highlight-add-start */
|
||||
const transformConditions = createConditionTransformer(
|
||||
permissionsRegistry.getPermissionRuleset(todoListPermissionResourceRef)
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
/* highlight-remove-next-line */
|
||||
router.get('/todos', async (_req, res) => {
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { createPermissionResourceRef } from '@backstage/plugin-permission-node';
|
||||
import { permissionsRegistryServiceFactory } from './permissionsRegistryServiceFactory';
|
||||
|
||||
describe('permissionsRegistryServiceFactory', () => {
|
||||
it('should reject resource refs from other plugins', async () => {
|
||||
await expect(
|
||||
startTestBackend({
|
||||
features: [
|
||||
permissionsRegistryServiceFactory,
|
||||
createBackendPlugin({
|
||||
pluginId: 'test',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: { permissionsRegistry: coreServices.permissionsRegistry },
|
||||
async init({ permissionsRegistry }) {
|
||||
permissionsRegistry.addResourceType({
|
||||
resourceRef: createPermissionResourceRef<
|
||||
unknown,
|
||||
unknown
|
||||
>().with({
|
||||
pluginId: 'other',
|
||||
resourceType: 'some-resource',
|
||||
}),
|
||||
rules: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'"`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
startTestBackend({
|
||||
features: [
|
||||
permissionsRegistryServiceFactory,
|
||||
createBackendPlugin({
|
||||
pluginId: 'test',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: { permissionsRegistry: coreServices.permissionsRegistry },
|
||||
async init({ permissionsRegistry }) {
|
||||
permissionsRegistry.getPermissionRuleset(
|
||||
createPermissionResourceRef<unknown, unknown>().with({
|
||||
pluginId: 'other',
|
||||
resourceType: 'some-resource',
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
+26
-4
@@ -15,10 +15,22 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
PermissionsRegistryService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
|
||||
import {
|
||||
PermissionResourceRef,
|
||||
createPermissionIntegrationRouter,
|
||||
} from '@backstage/plugin-permission-node';
|
||||
|
||||
function assertRefPluginId(ref: PermissionResourceRef, pluginId: string) {
|
||||
if (ref.pluginId !== pluginId) {
|
||||
throw new Error(
|
||||
`Resource type '${ref.resourceType}' belongs to plugin '${ref.pluginId}', but was used with plugin '${pluginId}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission system integration for registering resources and permissions.
|
||||
@@ -34,9 +46,11 @@ export const permissionsRegistryServiceFactory = createServiceFactory({
|
||||
deps: {
|
||||
lifecycle: coreServices.lifecycle,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ httpRouter, lifecycle }) {
|
||||
async factory({ httpRouter, lifecycle, pluginMetadata }) {
|
||||
const router = createPermissionIntegrationRouter();
|
||||
const pluginId = pluginMetadata.getId();
|
||||
|
||||
httpRouter.use(router);
|
||||
|
||||
@@ -52,7 +66,11 @@ export const permissionsRegistryServiceFactory = createServiceFactory({
|
||||
'Cannot add permission resource types after the plugin has started',
|
||||
);
|
||||
}
|
||||
router.addResourceType(resource);
|
||||
assertRefPluginId(resource.resourceRef, pluginId);
|
||||
router.addResourceType({
|
||||
...resource,
|
||||
resourceType: resource.resourceRef.resourceType,
|
||||
});
|
||||
},
|
||||
addPermissions(permissions) {
|
||||
if (started) {
|
||||
@@ -70,6 +88,10 @@ export const permissionsRegistryServiceFactory = createServiceFactory({
|
||||
}
|
||||
router.addPermissionRules(rules);
|
||||
},
|
||||
};
|
||||
getPermissionRuleset(resourceRef) {
|
||||
assertRefPluginId(resourceRef, pluginId);
|
||||
return router.getPermissionRuleset(resourceRef);
|
||||
},
|
||||
} satisfies PermissionsRegistryService;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,7 +19,9 @@ import { Knex } from 'knex';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { PermissionAttributes } from '@backstage/plugin-permission-common';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import { PermissionResourceRef } from '@backstage/plugin-permission-node';
|
||||
import { PermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { PermissionRuleset } from '@backstage/plugin-permission-node';
|
||||
import { QueryPermissionRequest } from '@backstage/plugin-permission-common';
|
||||
import { QueryPermissionResponse } from '@backstage/plugin-permission-common';
|
||||
import { Readable } from 'stream';
|
||||
@@ -466,23 +468,34 @@ export interface LoggerService {
|
||||
export interface PermissionsRegistryService {
|
||||
addPermissionRules(rules: PermissionRule<any, any, string>[]): void;
|
||||
addPermissions(permissions: Permission[]): void;
|
||||
addResourceType<const TResourceType extends string, TResource>(
|
||||
addResourceType<const TResourceType extends string, TResource, TQuery>(
|
||||
options: PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
TResourceType,
|
||||
TResource
|
||||
TResource,
|
||||
TQuery
|
||||
>,
|
||||
): void;
|
||||
getPermissionRuleset<TResourceType extends string, TResource, TQuery>(
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,
|
||||
): PermissionRuleset<TResource, TQuery, TResourceType>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TQuery,
|
||||
> = {
|
||||
resourceType: TResourceType;
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>;
|
||||
permissions?: Array<Permission>;
|
||||
rules: PermissionRule<TResource, any, NoInfer_2<TResourceType>>[];
|
||||
getResources?(resourceRefs: string[]): Promise<Array<TResource | undefined>>;
|
||||
rules: PermissionRule<
|
||||
NoInfer_2<TResource>,
|
||||
NoInfer_2<TQuery>,
|
||||
NoInfer_2<TResourceType>
|
||||
>[];
|
||||
getResources?(
|
||||
resourceRefs: string[],
|
||||
): Promise<Array<NoInfer_2<TResource> | undefined>>;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '@backstage/plugin-permission-node';
|
||||
import {
|
||||
PermissionResourceRef,
|
||||
PermissionRule,
|
||||
PermissionRuleset,
|
||||
} from '@backstage/plugin-permission-node';
|
||||
|
||||
/**
|
||||
* Prevent use of type parameter from contributing to type inference.
|
||||
@@ -33,11 +37,12 @@ type NoInfer<T> = T extends infer S ? S : never;
|
||||
export type PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TQuery,
|
||||
> = {
|
||||
/**
|
||||
* The identifier for the resource type.
|
||||
* The {@link @backstage/plugin-permission-node#PermissionResourceRef} that identifies the resource type.
|
||||
*/
|
||||
resourceType: TResourceType;
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>;
|
||||
|
||||
/**
|
||||
* Permissions that are available for this resource type.
|
||||
@@ -47,7 +52,11 @@ export type PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
/**
|
||||
* Permission rules that are available for this resource type.
|
||||
*/
|
||||
rules: PermissionRule<TResource, any, NoInfer<TResourceType>>[];
|
||||
rules: PermissionRule<
|
||||
NoInfer<TResource>,
|
||||
NoInfer<TQuery>,
|
||||
NoInfer<TResourceType>
|
||||
>[];
|
||||
|
||||
/**
|
||||
* The function used to load associated resources based in the provided
|
||||
@@ -59,7 +68,9 @@ export type PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
* resolve conditional decisions except when requesting resources directly
|
||||
* from the plugin.
|
||||
*/
|
||||
getResources?(resourceRefs: string[]): Promise<Array<TResource | undefined>>;
|
||||
getResources?(
|
||||
resourceRefs: string[],
|
||||
): Promise<Array<NoInfer<TResource> | undefined>>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,10 +133,22 @@ export interface PermissionsRegistryService {
|
||||
* called by the `permission-backend` when authorization conditions relating
|
||||
* to this plugin need to be evaluated.
|
||||
*/
|
||||
addResourceType<const TResourceType extends string, TResource>(
|
||||
addResourceType<const TResourceType extends string, TResource, TQuery>(
|
||||
options: PermissionsRegistryServiceAddResourceTypeOptions<
|
||||
TResourceType,
|
||||
TResource
|
||||
TResource,
|
||||
TQuery
|
||||
>,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Returns the set of registered rules for this resource.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Primarily intended for use with {@link @backstage/plugin-permission-node#createConditionAuthorizer} and {@link @backstage/plugin-permission-node#createConditionTransformer}.
|
||||
*/
|
||||
getPermissionRuleset<TResourceType extends string, TResource, TQuery>(
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,
|
||||
): PermissionRuleset<TResource, TQuery, TResourceType>;
|
||||
}
|
||||
|
||||
@@ -489,6 +489,7 @@ export namespace mockServices {
|
||||
addPermissionRules: jest.fn(),
|
||||
addPermissions: jest.fn(),
|
||||
addResourceType: jest.fn(),
|
||||
getPermissionRuleset: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { createConditionExports } from '@backstage/plugin-permission-node';
|
||||
import { permissionRules } from './rules';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
|
||||
const { conditions, createConditionalDecision } = createConditionExports({
|
||||
pluginId: 'catalog',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
rules: permissionRules,
|
||||
});
|
||||
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { get } from 'lodash';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createPropertyRule = (propertyType: 'metadata' | 'spec') =>
|
||||
createCatalogPermissionRule({
|
||||
createPermissionRule({
|
||||
name: `HAS_${propertyType.toUpperCase()}`,
|
||||
description: `Allow entities with the specified ${propertyType} subfield`,
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
key: z
|
||||
.string()
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
* A catalog {@link @backstage/plugin-permission-node#PermissionRule} which
|
||||
@@ -26,10 +26,10 @@ import { createCatalogPermissionRule } from './util';
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const hasAnnotation = createCatalogPermissionRule({
|
||||
export const hasAnnotation = createPermissionRule({
|
||||
name: 'HAS_ANNOTATION',
|
||||
description: 'Allow entities with the specified annotation',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
annotation: z.string().describe('Name of the annotation to match on'),
|
||||
value: z
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
* A catalog {@link @backstage/plugin-permission-node#PermissionRule} which
|
||||
* filters for entities with a specified label in its metadata.
|
||||
* @alpha
|
||||
*/
|
||||
export const hasLabel = createCatalogPermissionRule({
|
||||
export const hasLabel = createPermissionRule({
|
||||
name: 'HAS_LABEL',
|
||||
description: 'Allow entities with the specified label',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
label: z.string().describe('Name of the label to match on'),
|
||||
}),
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
|
||||
/**
|
||||
* A catalog {@link @backstage/plugin-permission-node#PermissionRule} which
|
||||
* filters for entities with a specified kind.
|
||||
* @alpha
|
||||
*/
|
||||
export const isEntityKind = createCatalogPermissionRule({
|
||||
export const isEntityKind = createPermissionRule({
|
||||
name: 'IS_ENTITY_KIND',
|
||||
description: 'Allow entities matching a specified kind',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
kinds: z
|
||||
.array(z.string())
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { createPermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { z } from 'zod';
|
||||
import { createCatalogPermissionRule } from './util';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
|
||||
/**
|
||||
* A catalog {@link @backstage/plugin-permission-node#PermissionRule} which
|
||||
@@ -25,10 +25,10 @@ import { createCatalogPermissionRule } from './util';
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const isEntityOwner = createCatalogPermissionRule({
|
||||
export const isEntityOwner = createPermissionRule({
|
||||
name: 'IS_ENTITY_OWNER',
|
||||
description: 'Allow entities owned by a specified claim',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
paramsSchema: z.object({
|
||||
claims: z
|
||||
.array(z.string())
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { createHash } from 'crypto';
|
||||
import { Router } from 'express';
|
||||
import lodash, { keyBy } from 'lodash';
|
||||
import lodash from 'lodash';
|
||||
|
||||
import {
|
||||
AuditorService,
|
||||
@@ -118,6 +118,7 @@ import { DefaultLocationService } from './DefaultLocationService';
|
||||
import { DefaultRefreshService } from './DefaultRefreshService';
|
||||
import { basicEntityFilter } from './request';
|
||||
import { entitiesResponseToObjects } from './response';
|
||||
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
|
||||
/**
|
||||
* This is a duplicate of the alpha `CatalogPermissionRule` type, for use in the stable API.
|
||||
@@ -558,7 +559,13 @@ export class CatalogBuilder {
|
||||
const entitiesCatalog = new AuthorizedEntitiesCatalog(
|
||||
unauthorizedEntitiesCatalog,
|
||||
permissionsService,
|
||||
createConditionTransformer(this.permissionRules),
|
||||
permissionsRegistry
|
||||
? createConditionTransformer(
|
||||
permissionsRegistry.getPermissionRuleset(
|
||||
catalogEntityPermissionResourceRef,
|
||||
),
|
||||
)
|
||||
: createConditionTransformer(this.permissionRules),
|
||||
);
|
||||
|
||||
const catalogPermissionResource = {
|
||||
@@ -579,9 +586,10 @@ export class CatalogBuilder {
|
||||
},
|
||||
});
|
||||
|
||||
const entitiesByRef = keyBy(
|
||||
entitiesResponseToObjects(entities),
|
||||
stringifyEntityRef,
|
||||
const entitiesByRef = Object.fromEntries(
|
||||
entitiesResponseToObjects(entities)
|
||||
.filter((x): x is Entity => Boolean(x))
|
||||
.map(entity => [stringifyEntityRef(entity), entity]),
|
||||
);
|
||||
|
||||
return resourceRefs.map(
|
||||
@@ -597,7 +605,10 @@ export class CatalogBuilder {
|
||||
| ReturnType<typeof createPermissionIntegrationRouter>
|
||||
| undefined;
|
||||
if (permissionsRegistry) {
|
||||
permissionsRegistry.addResourceType(catalogPermissionResource);
|
||||
permissionsRegistry.addResourceType({
|
||||
...catalogPermissionResource,
|
||||
resourceRef: catalogEntityPermissionResourceRef,
|
||||
});
|
||||
} else {
|
||||
permissionIntegrationRouter = createPermissionIntegrationRouter(
|
||||
catalogPermissionResource,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { PermissionResourceRef } from '@backstage/plugin-permission-node';
|
||||
import { PermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
|
||||
import { PlaceholderResolver } from '@backstage/plugin-catalog-node';
|
||||
@@ -34,6 +35,14 @@ export interface CatalogAnalysisExtensionPoint {
|
||||
// @alpha (undocumented)
|
||||
export const catalogAnalysisExtensionPoint: ExtensionPoint<CatalogAnalysisExtensionPoint>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const catalogEntityPermissionResourceRef: PermissionResourceRef<
|
||||
Entity,
|
||||
EntitiesSearchFilter,
|
||||
'catalog-entity',
|
||||
'catalog'
|
||||
>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export interface CatalogLocationsExtensionPoint {
|
||||
setAllowedLocationTypes(locationTypes: Array<string>): void;
|
||||
|
||||
@@ -21,6 +21,19 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogServiceRef as _catalogServiceRef } from './catalogService';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { createPermissionResourceRef } from '@backstage/plugin-permission-node';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node';
|
||||
|
||||
/** @alpha */
|
||||
export const catalogEntityPermissionResourceRef = createPermissionResourceRef<
|
||||
Entity,
|
||||
EntitiesSearchFilter
|
||||
>().with({
|
||||
pluginId: 'catalog',
|
||||
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
|
||||
});
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -78,12 +78,33 @@ export type ConditionTransformer<TQuery> = (
|
||||
) => PermissionCriteria<TQuery>;
|
||||
|
||||
// @public
|
||||
export const createConditionAuthorizer: <TResource, TQuery>(
|
||||
export function createConditionAuthorizer<TResource>(
|
||||
permissionRuleset: PermissionRuleset<TResource>,
|
||||
): (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function createConditionAuthorizer<TResource, TQuery>(
|
||||
rules: PermissionRule<TResource, TQuery, string>[],
|
||||
) => (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
): (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
|
||||
// @public
|
||||
export const createConditionExports: <
|
||||
export function createConditionExports<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>,
|
||||
>(options: {
|
||||
resourceRef: PermissionResourceRef<TResource, any, TResourceType>;
|
||||
rules: TRules;
|
||||
}): {
|
||||
conditions: Conditions<TRules>;
|
||||
createConditionalDecision: (
|
||||
permission: ResourcePermission<TResourceType>,
|
||||
conditions: PermissionCriteria<PermissionCondition<TResourceType>>,
|
||||
) => ConditionalPolicyDecision;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function createConditionExports<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>,
|
||||
@@ -91,7 +112,7 @@ export const createConditionExports: <
|
||||
pluginId: string;
|
||||
resourceType: TResourceType;
|
||||
rules: TRules;
|
||||
}) => {
|
||||
}): {
|
||||
conditions: Conditions<TRules>;
|
||||
createConditionalDecision: (
|
||||
permission: ResourcePermission<TResourceType>,
|
||||
@@ -108,12 +129,15 @@ export const createConditionFactory: <
|
||||
) => (params: TParams) => PermissionCondition<TResourceType, TParams>;
|
||||
|
||||
// @public
|
||||
export const createConditionTransformer: <
|
||||
export function createConditionTransformer<TQuery>(
|
||||
permissionRuleset: PermissionRuleset<any, TQuery>,
|
||||
): ConditionTransformer<TQuery>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function createConditionTransformer<
|
||||
TQuery,
|
||||
TRules extends PermissionRule<any, TQuery, string>[],
|
||||
>(
|
||||
permissionRules: [...TRules],
|
||||
) => ConditionTransformer<TQuery>;
|
||||
>(permissionRules: [...TRules]): ConditionTransformer<TQuery>;
|
||||
|
||||
// @public
|
||||
export function createPermissionIntegrationRouter<
|
||||
@@ -149,6 +173,9 @@ export function createPermissionIntegrationRouter<
|
||||
TResource
|
||||
>,
|
||||
): void;
|
||||
getPermissionRuleset<TResource, TQuery, TResourceType extends string>(
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,
|
||||
): PermissionRuleset<TResource, TQuery, TResourceType>;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -164,15 +191,56 @@ export type CreatePermissionIntegrationRouterResourceOptions<
|
||||
) => Promise<Array<TResource | undefined>>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPermissionResourceRef<TResource, TQuery>(): {
|
||||
with<TPluginId extends string, TResourceType extends string>(options: {
|
||||
pluginId: TPluginId;
|
||||
resourceType: TResourceType;
|
||||
}): PermissionResourceRef<TResource, TQuery, TResourceType, TPluginId>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const createPermissionRule: <
|
||||
export function createPermissionRule<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput extends TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams = undefined,
|
||||
>(
|
||||
rule: CreatePermissionRuleOptions<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput,
|
||||
TResourceType,
|
||||
TParams
|
||||
>,
|
||||
): PermissionRule<TResource, TQuery, TResourceType, TParams>;
|
||||
|
||||
// @public @deprecated
|
||||
export function createPermissionRule<
|
||||
TResource,
|
||||
TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams = undefined,
|
||||
>(
|
||||
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
|
||||
) => PermissionRule<TResource, TQuery, TResourceType, TParams>;
|
||||
): PermissionRule<TResource, TQuery, TResourceType, TParams>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CreatePermissionRuleOptions<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput extends TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams,
|
||||
> = {
|
||||
name: string;
|
||||
description: string;
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>;
|
||||
paramsSchema?: z.ZodSchema<TParams>;
|
||||
apply(resource: TResource, params: NoInfer_2<TParams>): boolean;
|
||||
toQuery(params: NoInfer_2<TParams>): PermissionCriteria<TQueryOutput>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const isAndCriteria: <T>(
|
||||
@@ -189,7 +257,7 @@ export const isOrCriteria: <T>(
|
||||
criteria: PermissionCriteria<T>,
|
||||
) => criteria is AnyOfCriteria<T>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const makeCreatePermissionRule: <
|
||||
TResource,
|
||||
TQuery,
|
||||
@@ -253,6 +321,20 @@ export interface PermissionPolicy {
|
||||
handle(request: PolicyQuery, user?: PolicyQueryUser): Promise<PolicyDecision>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type PermissionResourceRef<
|
||||
TResource = unknown,
|
||||
TQuery = unknown,
|
||||
TResourceType extends string = string,
|
||||
TPluginId extends string = string,
|
||||
> = {
|
||||
readonly $$type: '@backstage/PermissionResourceRef';
|
||||
readonly pluginId: TPluginId;
|
||||
readonly resourceType: TResourceType;
|
||||
readonly TQuery: TQuery;
|
||||
readonly TResource: TResource;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type PermissionRule<
|
||||
TResource,
|
||||
@@ -268,6 +350,15 @@ export type PermissionRule<
|
||||
toQuery(params: NoInfer_2<TParams>): PermissionCriteria<TQuery>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type PermissionRuleset<
|
||||
TResource = unknown,
|
||||
TQuery = unknown,
|
||||
TResourceType extends string = string,
|
||||
> = {
|
||||
getRuleByName(name: string): PermissionRule<TResource, TQuery, TResourceType>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type PolicyQuery = {
|
||||
permission: Permission;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '../types';
|
||||
import { createConditionFactory } from './createConditionFactory';
|
||||
import { PermissionResourceRef } from './createPermissionResourceRef';
|
||||
|
||||
/**
|
||||
* A utility type for mapping a single {@link PermissionRule} to its
|
||||
@@ -73,7 +74,25 @@ export type Conditions<
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createConditionExports = <
|
||||
export function createConditionExports<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>,
|
||||
>(options: {
|
||||
resourceRef: PermissionResourceRef<TResource, any, TResourceType>;
|
||||
rules: TRules;
|
||||
}): {
|
||||
conditions: Conditions<TRules>;
|
||||
createConditionalDecision: (
|
||||
permission: ResourcePermission<TResourceType>,
|
||||
conditions: PermissionCriteria<PermissionCondition<TResourceType>>,
|
||||
) => ConditionalPolicyDecision;
|
||||
};
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use the version of `createConditionExports` that accepts a `resourceRef` option instead.
|
||||
*/
|
||||
export function createConditionExports<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>,
|
||||
@@ -87,8 +106,32 @@ export const createConditionExports = <
|
||||
permission: ResourcePermission<TResourceType>,
|
||||
conditions: PermissionCriteria<PermissionCondition<TResourceType>>,
|
||||
) => ConditionalPolicyDecision;
|
||||
} => {
|
||||
const { pluginId, resourceType, rules } = options;
|
||||
};
|
||||
export function createConditionExports<
|
||||
TResourceType extends string,
|
||||
TResource,
|
||||
TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>,
|
||||
>(
|
||||
options:
|
||||
| {
|
||||
resourceRef: PermissionResourceRef<TResource, any, TResourceType>;
|
||||
rules: TRules;
|
||||
}
|
||||
| {
|
||||
pluginId: string;
|
||||
resourceType: TResourceType;
|
||||
rules: TRules;
|
||||
},
|
||||
): {
|
||||
conditions: Conditions<TRules>;
|
||||
createConditionalDecision: (
|
||||
permission: ResourcePermission<TResourceType>,
|
||||
conditions: PermissionCriteria<PermissionCondition<TResourceType>>,
|
||||
) => ConditionalPolicyDecision;
|
||||
} {
|
||||
const { rules } = options;
|
||||
const { pluginId, resourceType } =
|
||||
'resourceRef' in options ? options.resourceRef : options;
|
||||
|
||||
return {
|
||||
conditions: Object.entries(rules).reduce(
|
||||
@@ -108,4 +151,4 @@ export const createConditionExports = <
|
||||
conditions,
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import { PermissionRule } from '../types';
|
||||
* The rule itself defines _how_ to check a given resource, whereas a condition also includes _what_
|
||||
* to verify.
|
||||
*
|
||||
* Plugin authors should generally use the {@link createConditionExports} in order to efficiently
|
||||
* Plugin authors should generally use the {@link (createConditionExports:1)} in order to efficiently
|
||||
* create multiple condition factories. This helper should generally only be used to construct
|
||||
* condition factories for third-party rules that aren't part of the backend plugin with which
|
||||
* they're intended to integrate.
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
PermissionCondition,
|
||||
PermissionCriteria,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '../types';
|
||||
import { PermissionRule, PermissionRuleset } from '../types';
|
||||
import {
|
||||
createGetRule,
|
||||
isAndCriteria,
|
||||
@@ -76,13 +76,26 @@ export type ConditionTransformer<TQuery> = (
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createConditionTransformer = <
|
||||
export function createConditionTransformer<TQuery>(
|
||||
permissionRuleset: PermissionRuleset<any, TQuery>,
|
||||
): ConditionTransformer<TQuery>;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use the version of `createConditionTransformer` that accepts a `PermissionRuleset` instead.
|
||||
*/
|
||||
export function createConditionTransformer<
|
||||
TQuery,
|
||||
TRules extends PermissionRule<any, TQuery, string>[],
|
||||
>(
|
||||
permissionRules: [...TRules],
|
||||
): ConditionTransformer<TQuery> => {
|
||||
const getRule = createGetRule(permissionRules);
|
||||
>(permissionRules: [...TRules]): ConditionTransformer<TQuery>;
|
||||
export function createConditionTransformer<TQuery>(
|
||||
permissionRules:
|
||||
| PermissionRule<any, TQuery, string>[]
|
||||
| PermissionRuleset<any, TQuery>,
|
||||
): ConditionTransformer<TQuery> {
|
||||
const getRule =
|
||||
'getRuleByName' in permissionRules
|
||||
? (n: string) => permissionRules.getRuleByName(n)
|
||||
: createGetRule(permissionRules);
|
||||
|
||||
return conditions => mapConditions(conditions, getRule);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
PermissionCriteria,
|
||||
PolicyDecision,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '../types';
|
||||
import { PermissionRule, PermissionRuleset } from '../types';
|
||||
import {
|
||||
NoInfer,
|
||||
createGetRule,
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
isOrCriteria,
|
||||
} from './util';
|
||||
import { NotImplementedError } from '@backstage/errors';
|
||||
import { PermissionResourceRef } from './createPermissionResourceRef';
|
||||
|
||||
const permissionCriteriaSchema: z.ZodSchema<
|
||||
PermissionCriteria<PermissionCondition>
|
||||
@@ -163,10 +164,25 @@ const applyConditions = <TResourceType extends string, TResource>(
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createConditionAuthorizer = <TResource, TQuery>(
|
||||
export function createConditionAuthorizer<TResource>(
|
||||
permissionRuleset: PermissionRuleset<TResource>,
|
||||
): (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use the version of `createConditionAuthorizer` that accepts a `PermissionRuleset` instead.
|
||||
*/
|
||||
export function createConditionAuthorizer<TResource, TQuery>(
|
||||
rules: PermissionRule<TResource, TQuery, string>[],
|
||||
) => {
|
||||
const getRule = createGetRule(rules);
|
||||
): (decision: PolicyDecision, resource: TResource | undefined) => boolean;
|
||||
export function createConditionAuthorizer<TResource, TQuery>(
|
||||
rules:
|
||||
| PermissionRule<TResource, TQuery, string>[]
|
||||
| PermissionRuleset<TResource>,
|
||||
): (decision: PolicyDecision, resource: TResource | undefined) => boolean {
|
||||
const getRule =
|
||||
'getRuleByName' in rules
|
||||
? (n: string) => rules.getRuleByName(n)
|
||||
: createGetRule(rules);
|
||||
|
||||
return (
|
||||
decision: PolicyDecision,
|
||||
@@ -178,7 +194,7 @@ export const createConditionAuthorizer = <TResource, TQuery>(
|
||||
|
||||
return decision.result === AuthorizeResult.ALLOW;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a permission integration router specific
|
||||
@@ -426,6 +442,9 @@ export function createPermissionIntegrationRouter<
|
||||
TResource
|
||||
>,
|
||||
): void;
|
||||
getPermissionRuleset<TResource, TQuery, TResourceType extends string>(
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,
|
||||
): PermissionRuleset<TResource, TQuery, TResourceType>;
|
||||
} {
|
||||
const store = new PermissionIntegrationMetadataStore();
|
||||
|
||||
@@ -515,5 +534,12 @@ export function createPermissionIntegrationRouter<
|
||||
) {
|
||||
store.addResourceType(resource);
|
||||
},
|
||||
getPermissionRuleset<TResource, TQuery, TResourceType extends string>(
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,
|
||||
): PermissionRuleset<TResource, TQuery, TResourceType> {
|
||||
return {
|
||||
getRuleByName: store.getRuleMapper(resourceRef.resourceType),
|
||||
} as PermissionRuleset<TResource, TQuery, TResourceType>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type PermissionResourceRef<
|
||||
TResource = unknown,
|
||||
TQuery = unknown,
|
||||
TResourceType extends string = string,
|
||||
TPluginId extends string = string,
|
||||
> = {
|
||||
readonly $$type: '@backstage/PermissionResourceRef';
|
||||
readonly pluginId: TPluginId;
|
||||
readonly resourceType: TResourceType;
|
||||
readonly TQuery: TQuery;
|
||||
readonly TResource: TResource;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function createPermissionResourceRef<TResource, TQuery>(): {
|
||||
with<TPluginId extends string, TResourceType extends string>(options: {
|
||||
pluginId: TPluginId;
|
||||
resourceType: TResourceType;
|
||||
}): PermissionResourceRef<TResource, TQuery, TResourceType, TPluginId>;
|
||||
} {
|
||||
return {
|
||||
with<TPluginId extends string, TResourceType extends string>(options: {
|
||||
pluginId: TPluginId;
|
||||
resourceType: TResourceType;
|
||||
}): PermissionResourceRef<TResource, TQuery, TResourceType, TPluginId> {
|
||||
return {
|
||||
$$type: '@backstage/PermissionResourceRef',
|
||||
pluginId: options.pluginId,
|
||||
resourceType: options.resourceType,
|
||||
TQuery: null as TQuery,
|
||||
TResource: null as TResource,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -14,22 +14,106 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
PermissionCriteria,
|
||||
PermissionRuleParams,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '../types';
|
||||
import { z } from 'zod';
|
||||
import { PermissionResourceRef } from './createPermissionResourceRef';
|
||||
import { NoInfer } from './util';
|
||||
|
||||
/**
|
||||
* Helper function to ensure that {@link PermissionRule} definitions are typed correctly.
|
||||
* @public
|
||||
*/
|
||||
export type CreatePermissionRuleOptions<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput extends TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams,
|
||||
> = {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>;
|
||||
|
||||
/**
|
||||
* A ZodSchema that reflects the structure of the parameters that are passed to
|
||||
*/
|
||||
paramsSchema?: z.ZodSchema<TParams>;
|
||||
|
||||
/**
|
||||
* Apply this rule to a resource already loaded from a backing data source. The params are
|
||||
* arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the
|
||||
* params.
|
||||
*/
|
||||
apply(resource: TResource, params: NoInfer<TParams>): boolean;
|
||||
|
||||
/**
|
||||
* Translate this rule to criteria suitable for use in querying a backing data store. The criteria
|
||||
* can be used for loading a collection of resources efficiently with conditional criteria already
|
||||
* applied.
|
||||
*/
|
||||
toQuery(params: NoInfer<TParams>): PermissionCriteria<TQueryOutput>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create a {@link PermissionRule} for a specific resource type using a {@link PermissionResourceRef}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createPermissionRule = <
|
||||
export function createPermissionRule<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput extends TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams = undefined,
|
||||
>(
|
||||
rule: CreatePermissionRuleOptions<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput,
|
||||
TResourceType,
|
||||
TParams
|
||||
>,
|
||||
): PermissionRule<TResource, TQuery, TResourceType, TParams>;
|
||||
/**
|
||||
* Helper function to ensure that {@link PermissionRule} definitions are typed correctly.
|
||||
*
|
||||
* @deprecated Use the version of `createPermissionRule` that accepts a `resourceRef` option instead.
|
||||
* @public
|
||||
*/
|
||||
export function createPermissionRule<
|
||||
TResource,
|
||||
TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams = undefined,
|
||||
>(
|
||||
rule: PermissionRule<TResource, TQuery, TResourceType, TParams>,
|
||||
) => rule;
|
||||
): PermissionRule<TResource, TQuery, TResourceType, TParams>;
|
||||
export function createPermissionRule<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput extends TQuery,
|
||||
TResourceType extends string,
|
||||
TParams extends PermissionRuleParams = undefined,
|
||||
>(
|
||||
rule:
|
||||
| PermissionRule<TResource, TQuery, TResourceType, TParams>
|
||||
| CreatePermissionRuleOptions<
|
||||
TResource,
|
||||
TQuery,
|
||||
TQueryOutput,
|
||||
TResourceType,
|
||||
TParams
|
||||
>,
|
||||
): PermissionRule<TResource, TQuery, TResourceType, TParams> {
|
||||
if ('resourceRef' in rule) {
|
||||
return { ...rule, resourceType: rule.resourceRef.resourceType };
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for making plugin-specific createPermissionRule functions, that have
|
||||
@@ -38,6 +122,7 @@ export const createPermissionRule = <
|
||||
* consistent types for the resource and query.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link (createPermissionRule:1)} directly instead with the resourceRef option.
|
||||
*/
|
||||
export const makeCreatePermissionRule =
|
||||
<TResource, TQuery, TResourceType extends string>() =>
|
||||
|
||||
@@ -19,4 +19,8 @@ export * from './createConditionExports';
|
||||
export * from './createConditionTransformer';
|
||||
export * from './createPermissionIntegrationRouter';
|
||||
export * from './createPermissionRule';
|
||||
export {
|
||||
createPermissionResourceRef,
|
||||
type PermissionResourceRef,
|
||||
} from './createPermissionResourceRef';
|
||||
export { isAndCriteria, isOrCriteria, isNotCriteria } from './util';
|
||||
|
||||
@@ -66,3 +66,27 @@ export type PermissionRule<
|
||||
*/
|
||||
toQuery(params: NoInfer<TParams>): PermissionCriteria<TQuery>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of registered rules for a particular resource type.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Accessed via {@link @backstage/backend-plugin-api#PermissionsRegistryService.getPermissionRuleset}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PermissionRuleset<
|
||||
TResource = unknown,
|
||||
TQuery = unknown,
|
||||
TResourceType extends string = string,
|
||||
> = {
|
||||
/**
|
||||
* Returns a resource permission rule by name.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Will throw an error if a rule with the provided name does not exist.
|
||||
*/
|
||||
getRuleByName(name: string): PermissionRule<TResource, TQuery, TResourceType>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user