permission-node: add PermissionResourceRef

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-01-30 09:38:20 +01:00
parent 713c3b0d8a
commit a9621deada
8 changed files with 272 additions and 18 deletions
+5
View File
@@ -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.
+68 -5
View File
@@ -83,7 +83,23 @@ export const createConditionAuthorizer: <TResource, TQuery>(
) => (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 +107,7 @@ export const createConditionExports: <
pluginId: string;
resourceType: TResourceType;
rules: TRules;
}) => {
}): {
conditions: Conditions<TRules>;
createConditionalDecision: (
permission: ResourcePermission<TResourceType>,
@@ -164,15 +180,48 @@ 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,
TResourceType extends string,
TParams extends PermissionRuleParams = undefined,
>(
rule: CreatePermissionRuleOptions<TResource, TQuery, 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,
TResourceType extends string,
TParams extends PermissionRuleParams = 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<TQuery>;
};
// @public
export const isAndCriteria: <T>(
@@ -189,7 +238,7 @@ export const isOrCriteria: <T>(
criteria: PermissionCriteria<T>,
) => criteria is AnyOfCriteria<T>;
// @public
// @public @deprecated
export const makeCreatePermissionRule: <
TResource,
TQuery,
@@ -253,6 +302,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,
@@ -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.
@@ -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,22 @@ const applyConditions = <TResourceType extends string, TResource>(
*
* @public
*/
export const createConditionAuthorizer = <TResource, TQuery>(
export function createConditionAuthorizer<TResource>(
permissionRuleAccessor: PermissionRuleAccessor<TResource>,
): (decision: PolicyDecision, resource: TResource | undefined) => boolean;
/**
* @public
* @deprecated Use the version of `createConditionAuthorizer` that accepts a `PermissionRuleAccessor` 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>[]
| PermissionRuleAccessor<TResource>,
): (decision: PolicyDecision, resource: TResource | undefined) => boolean {
const getRule = typeof rules === 'function' ? rules : createGetRule(rules);
return (
decision: PolicyDecision,
@@ -178,7 +191,7 @@ export const createConditionAuthorizer = <TResource, TQuery>(
return decision.result === AuthorizeResult.ALLOW;
};
};
}
/**
* Options for creating a permission integration router specific
@@ -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,91 @@
* 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,
TResourceType extends string,
TParams extends PermissionRuleParams = 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<TQuery>;
};
/**
* 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,
TResourceType extends string,
TParams extends PermissionRuleParams = undefined,
>(
rule: CreatePermissionRuleOptions<TResource, TQuery, 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,
TResourceType extends string,
TParams extends PermissionRuleParams = undefined,
>(
rule:
| PermissionRule<TResource, TQuery, TResourceType, TParams>
| CreatePermissionRuleOptions<TResource, TQuery, 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 +107,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';