UNPKG

@backstage/plugin-permission-node

Version:

Common permission and authorization utilities for backend plugins

498 lines (486 loc) • 22.7 kB
import { PermissionRuleParams, PermissionCriteria, PermissionCondition, ResourcePermission, ConditionalPolicyDecision, IdentifiedPermissionMessage, DefinitivePolicyDecision, AuthorizeResult, MetadataResponseSerializedRule as MetadataResponseSerializedRule$1, MetadataResponse as MetadataResponse$1, PolicyDecision, Permission, AllOfCriteria, AnyOfCriteria, NotCriteria, QueryPermissionRequest, AuthorizePermissionRequest, AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { z } from 'zod'; import express from 'express'; import { BackstageUserIdentity } from '@backstage/plugin-auth-node'; import { BackstageCredentials, BackstageUserInfo, PermissionsService, DiscoveryService, AuthService, PermissionsServiceRequestOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; /** * Prevent use of type parameter from contributing to type inference. * * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 * * @ignore */ type NoInfer<T> = T extends infer S ? S : never; /** * A conditional rule that can be provided in an * {@link @backstage/plugin-permission-common#AuthorizeDecision} response to an authorization request. * * @remarks * * Rules can either be evaluated against a resource loaded in memory, or used as filters when * loading a collection of resources from a data source. The `apply` and `toQuery` methods implement * these two concepts. * * The two operations should always have the same logical result. If they don’t, the effective * outcome of an authorization operation will sometimes differ depending on how the authorization * check was performed. * * @public */ type PermissionRule<TResource, TQuery, TResourceType extends string, TParams extends PermissionRuleParams = PermissionRuleParams> = { name: string; description: string; resourceType: 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>; }; /** * A set of registered rules for a particular resource type. * * @remarks * * Accessed via {@link @backstage/backend-plugin-api#PermissionsRegistryService.getPermissionRuleset}. * * @public */ 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>; }; /** * Creates a condition factory function for a given authorization rule and parameter types. * * @remarks * * For example, an isEntityOwner rule for catalog entities might take an array of entityRef strings. * 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: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. * * @public */ declare const createConditionFactory: <TResourceType extends string, TParams extends PermissionRuleParams = PermissionRuleParams>(rule: PermissionRule<unknown, unknown, TResourceType, TParams>) => (params: TParams) => PermissionCondition<TResourceType, TParams>; /** * @public */ 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 */ declare function createPermissionResourceRef<TResource, TQuery>(): { with<TPluginId extends string, TResourceType extends string>(options: { pluginId: TPluginId; resourceType: TResourceType; }): PermissionResourceRef<TResource, TQuery, TResourceType, TPluginId>; }; /** * A utility type for mapping a single {@link PermissionRule} to its * corresponding {@link @backstage/plugin-permission-common#PermissionCondition}. * * @public */ type Condition<TRule> = TRule extends PermissionRule<any, any, infer TResourceType, infer TParams> ? undefined extends TParams ? () => PermissionCondition<TResourceType, TParams> : (params: TParams) => PermissionCondition<TResourceType, TParams> : never; /** * A utility type for mapping {@link PermissionRule}s to their corresponding * {@link @backstage/plugin-permission-common#PermissionCondition}s. * * @public */ type Conditions<TRules extends Record<string, PermissionRule<any, any, any>>> = { [Name in keyof TRules]: Condition<TRules[Name]>; }; /** * Creates the recommended condition-related exports for a given plugin based on * the built-in {@link PermissionRule}s it supports. * * @remarks * * The function returns a `conditions` object containing a * {@link @backstage/plugin-permission-common#PermissionCondition} factory for * each of the supplied {@link PermissionRule}s, along with a * `createConditionalDecision` function which builds the wrapper object needed * to enclose conditions when authoring {@link PermissionPolicy} * implementations. * * Plugin authors should generally call this method with all the built-in * {@link PermissionRule}s the plugin supports, and export the resulting * `conditions` object and `createConditionalDecision` function so that they can * be used by {@link PermissionPolicy} authors. * * @public */ declare 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. */ declare function createConditionExports<TResourceType extends string, TResource, TRules extends Record<string, PermissionRule<TResource, any, TResourceType>>>(options: { pluginId: string; resourceType: TResourceType; rules: TRules; }): { conditions: Conditions<TRules>; createConditionalDecision: (permission: ResourcePermission<TResourceType>, conditions: PermissionCriteria<PermissionCondition<TResourceType>>) => ConditionalPolicyDecision; }; /** * A function which accepts {@link @backstage/plugin-permission-common#PermissionCondition}s * logically grouped in a {@link @backstage/plugin-permission-common#PermissionCriteria} * object, and transforms the {@link @backstage/plugin-permission-common#PermissionCondition}s * into plugin specific query fragments while retaining the enclosing criteria shape. * * @public */ type ConditionTransformer<TQuery> = (conditions: PermissionCriteria<PermissionCondition>) => PermissionCriteria<TQuery>; /** * A higher-order helper function which accepts an array of * {@link PermissionRule}s, and returns a {@link ConditionTransformer} * which transforms input conditions into equivalent plugin-specific * query fragments using the supplied rules. * * @public */ declare function createConditionTransformer<TQuery>(permissionRuleset: PermissionRuleset<any, TQuery>): ConditionTransformer<TQuery>; /** * @public * @deprecated Use the version of `createConditionTransformer` that accepts a `PermissionRuleset` instead. */ declare function createConditionTransformer<TQuery, TRules extends PermissionRule<any, TQuery, string>[]>(permissionRules: [...TRules]): ConditionTransformer<TQuery>; /** * A request to load the referenced resource and apply conditions in order to * finalize a conditional authorization response. * * @public */ type ApplyConditionsRequestEntry = IdentifiedPermissionMessage<{ resourceRef: string | string[]; resourceType: string; conditions: PermissionCriteria<PermissionCondition>; }>; /** * A batch of {@link ApplyConditionsRequestEntry} objects. * * @public */ type ApplyConditionsRequest = { items: ApplyConditionsRequestEntry[]; }; /** * The result of applying the conditions, expressed as a definitive authorize * result of ALLOW or DENY. * * @public */ type ApplyConditionsResponseEntry = IdentifiedPermissionMessage<DefinitivePolicyDecision | { result: Array<AuthorizeResult.ALLOW | AuthorizeResult.DENY>; }>; /** * A batch of {@link ApplyConditionsResponseEntry} objects. * * @public */ type ApplyConditionsResponse = { items: ApplyConditionsResponseEntry[]; }; /** * Serialized permission rules, with the paramsSchema * converted from a ZodSchema to a JsonSchema. * * @public * @deprecated Please import from `@backstage/plugin-permission-common` instead. */ type MetadataResponseSerializedRule = MetadataResponseSerializedRule$1; /** * Response type for the .metadata endpoint. * * @public * @deprecated Please import from `@backstage/plugin-permission-common` instead. */ type MetadataResponse = MetadataResponse$1; /** * Takes some permission conditions and returns a definitive authorization result * on the resource to which they apply. * * @public */ declare function createConditionAuthorizer<TResource>(permissionRuleset: PermissionRuleset<TResource>): (decision: PolicyDecision, resource: TResource | undefined) => boolean; /** * @public * @deprecated Use the version of `createConditionAuthorizer` that accepts a `PermissionRuleset` instead. */ declare function createConditionAuthorizer<TResource, TQuery>(rules: PermissionRule<TResource, TQuery, string>[]): (decision: PolicyDecision, resource: TResource | undefined) => boolean; /** * Options for creating a permission integration router specific * for a particular resource type. * * @public * @deprecated {@link createPermissionIntegrationRouter} is deprecated */ type CreatePermissionIntegrationRouterResourceOptions<TResourceType extends string, TResource> = { resourceType: TResourceType; permissions?: Array<Permission>; rules: PermissionRule<TResource, any, NoInfer<TResourceType>>[]; getResources?: (resourceRefs: string[]) => Promise<Array<TResource | undefined>>; }; /** * Options for creating a permission integration router exposing * permissions and rules from multiple resource types. * * @public * @deprecated {@link createPermissionIntegrationRouter} is deprecated */ type PermissionIntegrationRouterOptions<TResourceType1 extends string = string, TResource1 = any, TResourceType2 extends string = string, TResource2 = any, TResourceType3 extends string = string, TResource3 = any> = { resources: Readonly<[ CreatePermissionIntegrationRouterResourceOptions<TResourceType1, TResource1> ] | [ CreatePermissionIntegrationRouterResourceOptions<TResourceType1, TResource1>, CreatePermissionIntegrationRouterResourceOptions<TResourceType2, TResource2> ] | [ CreatePermissionIntegrationRouterResourceOptions<TResourceType1, TResource1>, CreatePermissionIntegrationRouterResourceOptions<TResourceType2, TResource2>, CreatePermissionIntegrationRouterResourceOptions<TResourceType3, TResource3> ]>; }; /** * Create an express Router which provides an authorization route to allow * integration between the permission backend and other Backstage backend * plugins. Plugin owners that wish to support conditional authorization for * their resources should add the router created by this function to their * express app inside their `createRouter` implementation. * * In case the `permissions` option is provided, the router also * provides a route that exposes permissions and routes of a plugin. * * In case resources is provided, the routes can handle permissions * for multiple resource types. * * @remarks * * To make this concrete, we can use the Backstage software catalog as an * example. The catalog has conditional rules around access to specific * _entities_ in the catalog. The _type_ of resource is captured here as * `resourceType`, a string identifier (`catalog-entity` in this example) that * can be provided with permission definitions. This is merely a _type_ to * verify that conditions in an authorization policy are constructed correctly, * not a reference to a specific resource. * * The `rules` parameter is an array of {@link PermissionRule}s that introduce * conditional filtering logic for resources; for the catalog, these are things * like `isEntityOwner` or `hasAnnotation`. Rules describe how to filter a list * of resources, and the `conditions` returned allow these rules to be applied * with specific parameters (such as 'group:default/team-a', or * 'backstage.io/edit-url'). * * The `getResources` argument should load resources based on a reference * identifier. For the catalog, this is an * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be * any serialized format. This is used to construct the * `createPermissionIntegrationRouter`, a function to add an authorization route * to your backend plugin. This function will be called by the * `permission-backend` when authorization conditions relating to this plugin * need to be evaluated. * * @public * @deprecated use `PermissionRegistryService` instead, see {@link https://backstage.io/docs/backend-system/core-services/permissions-registry#migrating-from-createpermissionintegrationrouter | the migration section in the service docs} for more details. */ declare function createPermissionIntegrationRouter<TResourceType1 extends string, TResource1, TResourceType2 extends string, TResource2, TResourceType3 extends string, TResource3>(options?: { permissions: Array<Permission>; } | CreatePermissionIntegrationRouterResourceOptions<TResourceType1, TResource1> | PermissionIntegrationRouterOptions<TResourceType1, TResource1, TResourceType2, TResource2, TResourceType3, TResource3>): express.Router & { addPermissions(permissions: Permission[]): void; addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]): void; addResourceType<const TResourceType extends string, TResource>(resource: CreatePermissionIntegrationRouterResourceOptions<TResourceType, TResource>): void; getPermissionRuleset<TResource, TQuery, TResourceType extends string>(resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>): PermissionRuleset<TResource, TQuery, TResourceType>; }; /** * @public */ type CreatePermissionRuleOptions<TRef extends PermissionResourceRef, TParams extends PermissionRuleParams> = TRef extends PermissionResourceRef<infer IResource, infer IQuery, any> ? { name: string; description: string; resourceRef: TRef; /** * 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: IResource, 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<IQuery>; } : never; /** * Helper function to create a {@link PermissionRule} for a specific resource type using a {@link PermissionResourceRef}. * * @public */ declare function createPermissionRule<TRef extends PermissionResourceRef, TParams extends PermissionRuleParams = undefined>(rule: CreatePermissionRuleOptions<TRef, TParams>): PermissionRule<TRef['TResource'], TRef['TQuery'], TRef['resourceType'], 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 */ declare function createPermissionRule<TResource, TQuery, TResourceType extends string, TParams extends PermissionRuleParams = undefined>(rule: PermissionRule<TResource, TQuery, TResourceType, TParams>): PermissionRule<TResource, TQuery, TResourceType, TParams>; /** * Helper for making plugin-specific createPermissionRule functions, that have * the TResource and TQuery type parameters populated but infer the params from * the supplied rule. This helps ensure that rules created for this plugin use * consistent types for the resource and query. * * @public * @deprecated Use {@link (createPermissionRule:1)} directly instead with the resourceRef option. */ declare const makeCreatePermissionRule: <TResource, TQuery, TResourceType extends string>() => <TParams extends PermissionRuleParams = undefined>(rule: PermissionRule<TResource, TQuery, TResourceType, TParams>) => PermissionRule<TResource, TQuery, TResourceType, TParams>; /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria * @public * * @returns `true` if the permission criteria is of type allOf, * narrowing down `criteria` to the specific type. */ declare const isAndCriteria: <T>(criteria: PermissionCriteria<T>) => criteria is AllOfCriteria<T>; /** * Utility function used to parse a PermissionCriteria of type * @param criteria - a PermissionCriteria * @public * * @returns `true` if the permission criteria is of type anyOf, * narrowing down `criteria` to the specific type. */ declare const isOrCriteria: <T>(criteria: PermissionCriteria<T>) => criteria is AnyOfCriteria<T>; /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria * @public * * @returns `true` if the permission criteria is of type not, * narrowing down `criteria` to the specific type. */ declare const isNotCriteria: <T>(criteria: PermissionCriteria<T>) => criteria is NotCriteria<T>; /** * A query to be evaluated by the {@link PermissionPolicy}. * * @remarks * * Unlike other parts of the permission API, the policy does not accept a resource ref. This keeps * the policy decoupled from the resource loading and condition applying logic. * * @public */ type PolicyQuery = { permission: Permission; }; /** * The context within which a policy query is evaluated. * * @public */ type PolicyQueryUser = { /** * The token used to authenticate the user within Backstage. * * @deprecated User the `credentials` field in combination with `coreServices.auth` to generate a request token instead. */ token: string; /** * The number of seconds until the token expires. If not set, it can be assumed that the token does not expire. * * @deprecated This field is deprecated and will be removed in a future release. */ expiresInSeconds?: number; /** * A plaintext description of the identity that is encapsulated within the token. * * @deprecated Use the `info` field instead. */ identity: BackstageUserIdentity; /** * The credentials of the user making the request. */ credentials: BackstageCredentials; /** * The information for the user making the request. */ info: BackstageUserInfo; }; /** * A policy to evaluate authorization requests for any permissioned action performed in Backstage. * * @remarks * * This takes as input a permission and an optional Backstage identity, and should return ALLOW if * the user is permitted to execute that action; otherwise DENY. For permissions relating to * resources, such a catalog entities, a conditional response can also be returned. This states * that the action is allowed if the conditions provided hold true. * * Conditions are a rule, and parameters to evaluate against that rule. For example, the rule might * be `isOwner` and the parameters a collection of entityRefs; if one of the entityRefs matches * the `owner` field on a catalog entity, this would resolve to ALLOW. * * @public */ interface PermissionPolicy { handle(request: PolicyQuery, user?: PolicyQueryUser): Promise<PolicyDecision>; } /** * A thin wrapper around * {@link @backstage/plugin-permission-common#PermissionClient} that allows all * service-to-service requests. * @public */ declare class ServerPermissionClient implements PermissionsService { #private; static fromConfig(config: Config, options: { discovery: DiscoveryService; auth: AuthService; }): ServerPermissionClient; private constructor(); authorizeConditional(queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions): Promise<PolicyDecision[]>; authorize(requests: AuthorizePermissionRequest[], options?: PermissionsServiceRequestOptions): Promise<AuthorizePermissionResponse[]>; } export { ServerPermissionClient, createConditionAuthorizer, createConditionExports, createConditionFactory, createConditionTransformer, createPermissionIntegrationRouter, createPermissionResourceRef, createPermissionRule, isAndCriteria, isNotCriteria, isOrCriteria, makeCreatePermissionRule }; export type { ApplyConditionsRequest, ApplyConditionsRequestEntry, ApplyConditionsResponse, ApplyConditionsResponseEntry, Condition, ConditionTransformer, Conditions, CreatePermissionIntegrationRouterResourceOptions, CreatePermissionRuleOptions, MetadataResponse, MetadataResponseSerializedRule, PermissionIntegrationRouterOptions, PermissionPolicy, PermissionResourceRef, PermissionRule, PermissionRuleset, PolicyQuery, PolicyQueryUser };