@backstage/plugin-permission-node
Version:
Common permission and authorization utilities for backend plugins
497 lines (485 loc) • 22.8 kB
TypeScript
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 { type ApplyConditionsRequest, type ApplyConditionsRequestEntry, type ApplyConditionsResponse, type ApplyConditionsResponseEntry, type Condition, type ConditionTransformer, type Conditions, type CreatePermissionIntegrationRouterResourceOptions, type CreatePermissionRuleOptions, type MetadataResponse, type MetadataResponseSerializedRule, type PermissionIntegrationRouterOptions, type PermissionPolicy, type PermissionResourceRef, type PermissionRule, type PermissionRuleset, type PolicyQuery, type PolicyQueryUser, ServerPermissionClient, createConditionAuthorizer, createConditionExports, createConditionFactory, createConditionTransformer, createPermissionIntegrationRouter, createPermissionResourceRef, createPermissionRule, isAndCriteria, isNotCriteria, isOrCriteria, makeCreatePermissionRule };