@backstage/plugin-permission-node
Version:
Common permission and authorization utilities for backend plugins
498 lines (486 loc) • 22.7 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 { 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 };