@backstage/plugin-permission-node 0.8.9-next.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @backstage/plugin-permission-node
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 22ace13: **BREAKING** The `ServerPermissionClient` can no longer be instantiated with a `tokenManager` and must instead be instantiated with an `auth` service. If you are still on the legacy backend system, use `createLegacyAuthAdapters()` from `@backstage/backend-common` to create a compatible `auth` service.
8
+
9
+ ### Patch Changes
10
+
11
+ - 728e3e1: Improved type inference when passing a `PermissionResourceRef` to `createPermissionRule`.
12
+ - 876f2e1: Deprecated `createPermissionIntegrationRouter` and related types, which has been replaced by `PermissionRegistryService`. For more information, including how to migrate existing plugins, see the [service docs](https://backstage.io/docs/backend-system/core-services/permissions-registry).
13
+ - Updated dependencies
14
+ - @backstage/plugin-auth-node@0.6.1
15
+ - @backstage/backend-plugin-api@1.2.1
16
+ - @backstage/config@1.3.2
17
+ - @backstage/errors@1.2.7
18
+ - @backstage/plugin-permission-common@0.8.4
19
+
3
20
  ## 0.8.9-next.1
4
21
 
5
22
  ### Patch Changes
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var backendCommon = require('@backstage/backend-common');
4
3
  var pluginPermissionCommon = require('@backstage/plugin-permission-common');
5
4
 
6
5
  class ServerPermissionClient {
@@ -8,15 +7,9 @@ class ServerPermissionClient {
8
7
  #permissionClient;
9
8
  #permissionEnabled;
10
9
  static fromConfig(config, options) {
11
- const { discovery, tokenManager } = options;
10
+ const { auth, discovery } = options;
12
11
  const permissionClient = new pluginPermissionCommon.PermissionClient({ discovery, config });
13
12
  const permissionEnabled = config.getOptionalBoolean("permission.enabled") ?? false;
14
- if (permissionEnabled && tokenManager && tokenManager.isInsecureServerTokenManager) {
15
- throw new Error(
16
- "Service-to-service authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/auth/service-to-service-auth"
17
- );
18
- }
19
- const { auth } = backendCommon.createLegacyAuthAdapters(options);
20
13
  return new ServerPermissionClient({
21
14
  auth,
22
15
  permissionClient,
@@ -1 +1 @@
1
- {"version":3,"file":"ServerPermissionClient.cjs.js","sources":["../src/ServerPermissionClient.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n TokenManager,\n createLegacyAuthAdapters,\n} from '@backstage/backend-common';\nimport {\n AuthService,\n BackstageCredentials,\n BackstageServicePrincipal,\n DiscoveryService,\n PermissionsService,\n PermissionsServiceRequestOptions,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n AuthorizeResult,\n DefinitivePolicyDecision,\n Permission,\n PermissionClient,\n PolicyDecision,\n QueryPermissionRequest,\n} from '@backstage/plugin-permission-common';\n\n/**\n * A thin wrapper around\n * {@link @backstage/plugin-permission-common#PermissionClient} that allows all\n * service-to-service requests.\n * @public\n */\nexport class ServerPermissionClient implements PermissionsService {\n readonly #auth: AuthService;\n readonly #permissionClient: PermissionClient;\n readonly #permissionEnabled: boolean;\n\n static fromConfig(\n config: Config,\n options: {\n discovery: DiscoveryService;\n /** @deprecated This option will be removed in the future, provide a the auth option instead */\n tokenManager?: TokenManager;\n auth?: AuthService;\n },\n ) {\n const { discovery, tokenManager } = options;\n const permissionClient = new PermissionClient({ discovery, config });\n const permissionEnabled =\n config.getOptionalBoolean('permission.enabled') ?? false;\n\n if (\n permissionEnabled &&\n tokenManager &&\n (tokenManager as any).isInsecureServerTokenManager\n ) {\n throw new Error(\n 'Service-to-service authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/auth/service-to-service-auth',\n );\n }\n\n const { auth } = createLegacyAuthAdapters(options);\n\n return new ServerPermissionClient({\n auth,\n permissionClient,\n permissionEnabled,\n });\n }\n\n private constructor(options: {\n auth: AuthService;\n permissionClient: PermissionClient;\n permissionEnabled: boolean;\n }) {\n this.#auth = options.auth;\n this.#permissionClient = options.permissionClient;\n this.#permissionEnabled = options.permissionEnabled;\n }\n\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionsServiceRequestOptions,\n ): Promise<PolicyDecision[]> {\n const credentials = await this.#getIncomingCredentials(options);\n if (credentials && this.#auth.isPrincipal(credentials, 'service')) {\n return this.#servicePrincipalDecision(queries, credentials);\n } else if (!this.#permissionEnabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW }));\n }\n\n return this.#permissionClient.authorizeConditional(\n queries,\n await this.#getRequestOptions(options),\n );\n }\n\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionsServiceRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n const credentials = await this.#getIncomingCredentials(options);\n if (credentials && this.#auth.isPrincipal(credentials, 'service')) {\n return this.#servicePrincipalDecision(requests, credentials);\n } else if (!this.#permissionEnabled) {\n return requests.map(_ => ({ result: AuthorizeResult.ALLOW }));\n }\n\n return this.#permissionClient.authorize(\n requests,\n await this.#getRequestOptions(options),\n );\n }\n\n async #getRequestOptions(\n options?: PermissionsServiceRequestOptions,\n ): Promise<{ token?: string } | undefined> {\n if (options && 'credentials' in options) {\n if (this.#auth.isPrincipal(options.credentials, 'none')) {\n return {};\n }\n\n return this.#auth.getPluginRequestToken({\n onBehalfOf: options.credentials,\n targetPluginId: 'permission',\n });\n }\n\n return options;\n }\n\n async #getIncomingCredentials(\n options?: PermissionsServiceRequestOptions,\n ): Promise<BackstageCredentials | undefined> {\n if (options && 'credentials' in options) {\n return options.credentials;\n }\n\n return undefined;\n }\n\n /**\n * For service principals, we can always make an immediate definitive decision\n * based on their associated access restrictions (if any).\n */\n #servicePrincipalDecision(\n input: { permission: Permission }[],\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n ): DefinitivePolicyDecision[] {\n const { permissionNames, permissionAttributes } =\n credentials.principal.accessRestrictions ?? {};\n\n return input.map(item => {\n if (permissionNames && !permissionNames.includes(item.permission.name)) {\n return { result: AuthorizeResult.DENY };\n }\n\n if (permissionAttributes?.action) {\n const action = item.permission.attributes?.action;\n if (!action || !permissionAttributes.action.includes(action)) {\n return { result: AuthorizeResult.DENY };\n }\n }\n\n return { result: AuthorizeResult.ALLOW };\n });\n }\n}\n"],"names":["PermissionClient","createLegacyAuthAdapters","AuthorizeResult"],"mappings":"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"}
1
+ {"version":3,"file":"ServerPermissionClient.cjs.js","sources":["../src/ServerPermissionClient.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageServicePrincipal,\n DiscoveryService,\n PermissionsService,\n PermissionsServiceRequestOptions,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n AuthorizeResult,\n DefinitivePolicyDecision,\n Permission,\n PermissionClient,\n PolicyDecision,\n QueryPermissionRequest,\n} from '@backstage/plugin-permission-common';\n\n/**\n * A thin wrapper around\n * {@link @backstage/plugin-permission-common#PermissionClient} that allows all\n * service-to-service requests.\n * @public\n */\nexport class ServerPermissionClient implements PermissionsService {\n readonly #auth: AuthService;\n readonly #permissionClient: PermissionClient;\n readonly #permissionEnabled: boolean;\n\n static fromConfig(\n config: Config,\n options: {\n discovery: DiscoveryService;\n auth: AuthService;\n },\n ) {\n const { auth, discovery } = options;\n const permissionClient = new PermissionClient({ discovery, config });\n const permissionEnabled =\n config.getOptionalBoolean('permission.enabled') ?? false;\n\n return new ServerPermissionClient({\n auth,\n permissionClient,\n permissionEnabled,\n });\n }\n\n private constructor(options: {\n auth: AuthService;\n permissionClient: PermissionClient;\n permissionEnabled: boolean;\n }) {\n this.#auth = options.auth;\n this.#permissionClient = options.permissionClient;\n this.#permissionEnabled = options.permissionEnabled;\n }\n\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionsServiceRequestOptions,\n ): Promise<PolicyDecision[]> {\n const credentials = await this.#getIncomingCredentials(options);\n if (credentials && this.#auth.isPrincipal(credentials, 'service')) {\n return this.#servicePrincipalDecision(queries, credentials);\n } else if (!this.#permissionEnabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW }));\n }\n\n return this.#permissionClient.authorizeConditional(\n queries,\n await this.#getRequestOptions(options),\n );\n }\n\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionsServiceRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n const credentials = await this.#getIncomingCredentials(options);\n if (credentials && this.#auth.isPrincipal(credentials, 'service')) {\n return this.#servicePrincipalDecision(requests, credentials);\n } else if (!this.#permissionEnabled) {\n return requests.map(_ => ({ result: AuthorizeResult.ALLOW }));\n }\n\n return this.#permissionClient.authorize(\n requests,\n await this.#getRequestOptions(options),\n );\n }\n\n async #getRequestOptions(\n options?: PermissionsServiceRequestOptions,\n ): Promise<{ token?: string } | undefined> {\n if (options && 'credentials' in options) {\n if (this.#auth.isPrincipal(options.credentials, 'none')) {\n return {};\n }\n\n return this.#auth.getPluginRequestToken({\n onBehalfOf: options.credentials,\n targetPluginId: 'permission',\n });\n }\n\n return options;\n }\n\n async #getIncomingCredentials(\n options?: PermissionsServiceRequestOptions,\n ): Promise<BackstageCredentials | undefined> {\n if (options && 'credentials' in options) {\n return options.credentials;\n }\n\n return undefined;\n }\n\n /**\n * For service principals, we can always make an immediate definitive decision\n * based on their associated access restrictions (if any).\n */\n #servicePrincipalDecision(\n input: { permission: Permission }[],\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n ): DefinitivePolicyDecision[] {\n const { permissionNames, permissionAttributes } =\n credentials.principal.accessRestrictions ?? {};\n\n return input.map(item => {\n if (permissionNames && !permissionNames.includes(item.permission.name)) {\n return { result: AuthorizeResult.DENY };\n }\n\n if (permissionAttributes?.action) {\n const action = item.permission.attributes?.action;\n if (!action || !permissionAttributes.action.includes(action)) {\n return { result: AuthorizeResult.DENY };\n }\n }\n\n return { result: AuthorizeResult.ALLOW };\n });\n }\n}\n"],"names":["PermissionClient","AuthorizeResult"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,6 @@ import { z } from 'zod';
3
3
  import express from 'express';
4
4
  import { BackstageUserIdentity } from '@backstage/plugin-auth-node';
5
5
  import { BackstageCredentials, BackstageUserInfo, PermissionsService, DiscoveryService, AuthService, PermissionsServiceRequestOptions } from '@backstage/backend-plugin-api';
6
- import { TokenManager } from '@backstage/backend-common';
7
6
  import { Config } from '@backstage/config';
8
7
 
9
8
  /**
@@ -282,6 +281,7 @@ declare function createConditionAuthorizer<TResource, TQuery>(rules: PermissionR
282
281
  * for a particular resource type.
283
282
  *
284
283
  * @public
284
+ * @deprecated {@link createPermissionIntegrationRouter} is deprecated
285
285
  */
286
286
  type CreatePermissionIntegrationRouterResourceOptions<TResourceType extends string, TResource> = {
287
287
  resourceType: TResourceType;
@@ -294,6 +294,7 @@ type CreatePermissionIntegrationRouterResourceOptions<TResourceType extends stri
294
294
  * permissions and rules from multiple resource types.
295
295
  *
296
296
  * @public
297
+ * @deprecated {@link createPermissionIntegrationRouter} is deprecated
297
298
  */
298
299
  type PermissionIntegrationRouterOptions<TResourceType1 extends string = string, TResource1 = any, TResourceType2 extends string = string, TResource2 = any, TResourceType3 extends string = string, TResource3 = any> = {
299
300
  resources: Readonly<[
@@ -347,6 +348,7 @@ type PermissionIntegrationRouterOptions<TResourceType1 extends string = string,
347
348
  * need to be evaluated.
348
349
  *
349
350
  * @public
351
+ * @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.
350
352
  */
351
353
  declare function createPermissionIntegrationRouter<TResourceType1 extends string, TResource1, TResourceType2 extends string, TResource2, TResourceType3 extends string, TResource3>(options?: {
352
354
  permissions: Array<Permission>;
@@ -481,9 +483,7 @@ declare class ServerPermissionClient implements PermissionsService {
481
483
  #private;
482
484
  static fromConfig(config: Config, options: {
483
485
  discovery: DiscoveryService;
484
- /** @deprecated This option will be removed in the future, provide a the auth option instead */
485
- tokenManager?: TokenManager;
486
- auth?: AuthService;
486
+ auth: AuthService;
487
487
  }): ServerPermissionClient;
488
488
  private constructor();
489
489
  authorizeConditional(queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions): Promise<PolicyDecision[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"createPermissionIntegrationRouter.cjs.js","sources":["../../src/integration/createPermissionIntegrationRouter.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express, { Response } from 'express';\nimport Router from 'express-promise-router';\nimport { z } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport { InputError } from '@backstage/errors';\nimport {\n AuthorizeResult,\n DefinitivePolicyDecision,\n IdentifiedPermissionMessage,\n MetadataResponse as CommonMetadataResponse,\n MetadataResponseSerializedRule as CommonMetadataResponseSerializedRule,\n Permission,\n PermissionCondition,\n PermissionCriteria,\n PolicyDecision,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule, PermissionRuleset } from '../types';\nimport {\n NoInfer,\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\nimport { NotImplementedError } from '@backstage/errors';\nimport { PermissionResourceRef } from './createPermissionResourceRef';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z.union([\n z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }),\n z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }),\n z.object({ not: permissionCriteriaSchema }),\n z.object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n }),\n ]),\n);\n\nconst applyConditionsRequestSchema = z.object({\n items: z.array(\n z.object({\n id: z.string(),\n resourceRef: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ),\n});\n\n/**\n * A request to load the referenced resource and apply conditions in order to\n * finalize a conditional authorization response.\n *\n * @public\n */\nexport type ApplyConditionsRequestEntry = IdentifiedPermissionMessage<{\n resourceRef: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n}>;\n\n/**\n * A batch of {@link ApplyConditionsRequestEntry} objects.\n *\n * @public\n */\nexport type ApplyConditionsRequest = {\n items: ApplyConditionsRequestEntry[];\n};\n\n/**\n * The result of applying the conditions, expressed as a definitive authorize\n * result of ALLOW or DENY.\n *\n * @public\n */\nexport type ApplyConditionsResponseEntry =\n IdentifiedPermissionMessage<DefinitivePolicyDecision>;\n\n/**\n * A batch of {@link ApplyConditionsResponseEntry} objects.\n *\n * @public\n */\nexport type ApplyConditionsResponse = {\n items: ApplyConditionsResponseEntry[];\n};\n\n/**\n * Serialized permission rules, with the paramsSchema\n * converted from a ZodSchema to a JsonSchema.\n *\n * @public\n * @deprecated Please import from `@backstage/plugin-permission-common` instead.\n */\nexport type MetadataResponseSerializedRule =\n CommonMetadataResponseSerializedRule;\n\n/**\n * Response type for the .metadata endpoint.\n *\n * @public\n * @deprecated Please import from `@backstage/plugin-permission-common` instead.\n */\nexport type MetadataResponse = CommonMetadataResponse;\n\nconst applyConditions = <TResourceType extends string, TResource>(\n criteria: PermissionCriteria<PermissionCondition<TResourceType>>,\n resource: TResource | undefined,\n getRule: (name: string) => PermissionRule<TResource, unknown, TResourceType>,\n): boolean => {\n // If resource was not found, deny. This avoids leaking information from the\n // apply-conditions API which would allow a user to differentiate between\n // non-existent resources and resources to which they do not have access.\n if (resource === undefined) {\n return false;\n }\n\n if (isAndCriteria(criteria)) {\n return criteria.allOf.every(child =>\n applyConditions(child, resource, getRule),\n );\n } else if (isOrCriteria(criteria)) {\n return criteria.anyOf.some(child =>\n applyConditions(child, resource, getRule),\n );\n } else if (isNotCriteria(criteria)) {\n return !applyConditions(criteria.not, resource, getRule);\n }\n\n const rule = getRule(criteria.rule);\n const result = rule.paramsSchema?.safeParse(criteria.params);\n\n if (result && !result.success) {\n throw new InputError(`Parameters to rule are invalid`, result.error);\n }\n\n return rule.apply(resource, criteria.params ?? {});\n};\n\n/**\n * Takes some permission conditions and returns a definitive authorization result\n * on the resource to which they apply.\n *\n * @public\n */\nexport function createConditionAuthorizer<TResource>(\n permissionRuleset: PermissionRuleset<TResource>,\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean;\n/**\n * @public\n * @deprecated Use the version of `createConditionAuthorizer` that accepts a `PermissionRuleset` instead.\n */\nexport function createConditionAuthorizer<TResource, TQuery>(\n rules: PermissionRule<TResource, TQuery, string>[],\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean;\nexport function createConditionAuthorizer<TResource, TQuery>(\n rules:\n | PermissionRule<TResource, TQuery, string>[]\n | PermissionRuleset<TResource>,\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean {\n const getRule =\n 'getRuleByName' in rules\n ? (n: string) => rules.getRuleByName(n)\n : createGetRule(rules);\n\n return (\n decision: PolicyDecision,\n resource: TResource | undefined,\n ): boolean => {\n if (decision.result === AuthorizeResult.CONDITIONAL) {\n return applyConditions(decision.conditions, resource, getRule);\n }\n\n return decision.result === AuthorizeResult.ALLOW;\n };\n}\n\n/**\n * Options for creating a permission integration router specific\n * for a particular resource type.\n *\n * @public\n */\nexport type CreatePermissionIntegrationRouterResourceOptions<\n TResourceType extends string,\n TResource,\n> = {\n resourceType: TResourceType;\n permissions?: Array<Permission>;\n // Do not infer value of TResourceType from supplied rules.\n // instead only consider the resourceType parameter, and\n // consider any rules whose resource type does not match\n // to be an error.\n rules: PermissionRule<TResource, any, NoInfer<TResourceType>>[];\n getResources?: (\n resourceRefs: string[],\n ) => Promise<Array<TResource | undefined>>;\n};\n\n/**\n * Options for creating a permission integration router exposing\n * permissions and rules from multiple resource types.\n *\n * @public\n */\nexport type PermissionIntegrationRouterOptions<\n TResourceType1 extends string = string,\n TResource1 = any,\n TResourceType2 extends string = string,\n TResource2 = any,\n TResourceType3 extends string = string,\n TResource3 = any,\n> = {\n resources: Readonly<\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n ]\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType2,\n TResource2\n >,\n ]\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType2,\n TResource2\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType3,\n TResource3\n >,\n ]\n >;\n};\n\nclass PermissionIntegrationMetadataStore {\n readonly #rulesByTypeByName = new Map<\n string,\n Map<string, PermissionRule<unknown, unknown, string>>\n >();\n readonly #permissionsByName = new Map<string, Permission>();\n readonly #resourcesByType = new Map<\n string,\n CreatePermissionIntegrationRouterResourceOptions<string, unknown>\n >();\n readonly #serializedRules = new Array<MetadataResponseSerializedRule>();\n\n getSerializedMetadata(): MetadataResponse {\n return {\n permissions: Array.from(this.#permissionsByName.values()),\n rules: this.#serializedRules,\n };\n }\n\n hasResourceType(type: string): boolean {\n return this.#resourcesByType.has(type);\n }\n\n async getResources(\n resourceType: string,\n refs: string[],\n ): Promise<Record<string, unknown>> {\n const resource = this.#resourcesByType.get(resourceType);\n if (!resource?.getResources) {\n throw new NotImplementedError(\n `This plugin does not expose any permission rule or can't evaluate the conditions request for ${resourceType}`,\n );\n }\n\n const uniqueRefs = Array.from(new Set(refs));\n const resources = await resource.getResources(uniqueRefs);\n return Object.fromEntries(\n uniqueRefs.map((ref, index) => [ref, resources[index]]),\n );\n }\n\n getRuleMapper(resourceType: string) {\n return (name: string): PermissionRule<unknown, unknown, string> => {\n const rule = this.#rulesByTypeByName.get(resourceType)?.get(name);\n if (!rule) {\n throw new Error(\n `Permission rule '${name}' does not exist for resource type '${resourceType}'`,\n );\n }\n return rule;\n };\n }\n\n addPermissions(permissions: Permission[]) {\n for (const permission of permissions) {\n // Permission naming conflicts are silently ignored\n this.#permissionsByName.set(permission.name, permission);\n }\n }\n\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]) {\n for (const rule of rules) {\n const rulesByName =\n this.#rulesByTypeByName.get(rule.resourceType) ?? new Map();\n this.#rulesByTypeByName.set(rule.resourceType, rulesByName);\n\n if (rulesByName.has(rule.name)) {\n throw new Error(\n `Refused to add permission rule for type '${rule.resourceType}' with name '${rule.name}' because it already exists`,\n );\n }\n rulesByName.set(rule.name, rule);\n\n this.#serializedRules.push({\n name: rule.name,\n description: rule.description,\n resourceType: rule.resourceType,\n paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})),\n });\n }\n }\n\n addResourceType(\n resource: CreatePermissionIntegrationRouterResourceOptions<string, unknown>,\n ) {\n const { resourceType } = resource;\n\n if (this.#resourcesByType.has(resourceType)) {\n throw new Error(\n `Refused to add permission resource with type '${resourceType}' because it already exists`,\n );\n }\n this.#resourcesByType.set(resourceType, resource);\n\n if (resource.rules) {\n this.addPermissionRules(resource.rules);\n }\n\n if (resource.permissions) {\n this.addPermissions(resource.permissions);\n }\n }\n}\n\n/**\n * Create an express Router which provides an authorization route to allow\n * integration between the permission backend and other Backstage backend\n * plugins. Plugin owners that wish to support conditional authorization for\n * their resources should add the router created by this function to their\n * express app inside their `createRouter` implementation.\n *\n * In case the `permissions` option is provided, the router also\n * provides a route that exposes permissions and routes of a plugin.\n *\n * In case resources is provided, the routes can handle permissions\n * for multiple resource types.\n *\n * @remarks\n *\n * To make this concrete, we can use the Backstage software catalog as an\n * example. The catalog has conditional rules around access to specific\n * _entities_ in the catalog. The _type_ of resource is captured here as\n * `resourceType`, a string identifier (`catalog-entity` in this example) that\n * can be provided with permission definitions. This is merely a _type_ to\n * verify that conditions in an authorization policy are constructed correctly,\n * not a reference to a specific resource.\n *\n * The `rules` parameter is an array of {@link PermissionRule}s that introduce\n * conditional filtering logic for resources; for the catalog, these are things\n * like `isEntityOwner` or `hasAnnotation`. Rules describe how to filter a list\n * of resources, and the `conditions` returned allow these rules to be applied\n * with specific parameters (such as 'group:default/team-a', or\n * 'backstage.io/edit-url').\n *\n * The `getResources` argument should load resources based on a reference\n * identifier. For the catalog, this is an\n * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be\n * any serialized format. This is used to construct the\n * `createPermissionIntegrationRouter`, a function to add an authorization route\n * to your backend plugin. This function will be called by the\n * `permission-backend` when authorization conditions relating to this plugin\n * need to be evaluated.\n *\n * @public\n */\nexport function createPermissionIntegrationRouter<\n TResourceType1 extends string,\n TResource1,\n TResourceType2 extends string,\n TResource2,\n TResourceType3 extends string,\n TResource3,\n>(\n options?:\n | { permissions: Array<Permission> }\n | CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >\n | PermissionIntegrationRouterOptions<\n TResourceType1,\n TResource1,\n TResourceType2,\n TResource2,\n TResourceType3,\n TResource3\n >,\n): express.Router & {\n addPermissions(permissions: Permission[]): void;\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]): void;\n addResourceType<const TResourceType extends string, TResource>(\n resource: CreatePermissionIntegrationRouterResourceOptions<\n TResourceType,\n TResource\n >,\n ): void;\n getPermissionRuleset<TResource, TQuery, TResourceType extends string>(\n resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,\n ): PermissionRuleset<TResource, TQuery, TResourceType>;\n} {\n const store = new PermissionIntegrationMetadataStore();\n\n if (options) {\n if ('resources' in options) {\n // Not technically allowed by types, but it's historically been covered by tests\n if ('permissions' in options) {\n store.addPermissions(options.permissions as Permission[]);\n }\n\n for (const resource of options.resources) {\n store.addResourceType(resource);\n }\n } else if ('resourceType' in options) {\n store.addResourceType(options);\n } else {\n store.addPermissions(options.permissions);\n }\n }\n\n const router = Router();\n\n router.use('/.well-known/backstage/permissions/', express.json());\n\n router.get('/.well-known/backstage/permissions/metadata', (_, res) => {\n res.json(store.getSerializedMetadata());\n });\n\n router.post(\n '/.well-known/backstage/permissions/apply-conditions',\n async (req, res: Response<ApplyConditionsResponse | string>) => {\n const parseResult = applyConditionsRequestSchema.safeParse(req.body);\n if (!parseResult.success) {\n throw new InputError(parseResult.error.toString());\n }\n\n const { items: requests } = parseResult.data;\n\n const invalidResourceTypes = requests.filter(\n i => !store.hasResourceType(i.resourceType),\n );\n if (invalidResourceTypes.length) {\n throw new InputError(\n `Unexpected resource types: ${invalidResourceTypes\n .map(i => i.resourceType)\n .join(', ')}.`,\n );\n }\n\n const resourcesByType: Record<string, Record<string, any>> = {};\n for (const requestedType of new Set(requests.map(i => i.resourceType))) {\n resourcesByType[requestedType] = await store.getResources(\n requestedType,\n requests\n .filter(r => r.resourceType === requestedType)\n .map(i => i.resourceRef),\n );\n }\n\n res.json({\n items: requests.map(request => ({\n id: request.id,\n result: applyConditions(\n request.conditions,\n resourcesByType[request.resourceType][request.resourceRef],\n store.getRuleMapper(request.resourceType),\n )\n ? AuthorizeResult.ALLOW\n : AuthorizeResult.DENY,\n })),\n });\n },\n );\n\n return Object.assign(router, {\n addPermissions(permissions: Permission[]) {\n store.addPermissions(permissions);\n },\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]) {\n store.addPermissionRules(rules);\n },\n addResourceType<const TResourceType extends string, TResource>(\n resource: CreatePermissionIntegrationRouterResourceOptions<\n TResourceType,\n TResource\n >,\n ) {\n store.addResourceType(resource);\n },\n getPermissionRuleset<TResource, TQuery, TResourceType extends string>(\n resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,\n ): PermissionRuleset<TResource, TQuery, TResourceType> {\n return {\n getRuleByName: store.getRuleMapper(resourceRef.resourceType),\n } as PermissionRuleset<TResource, TQuery, TResourceType>;\n },\n });\n}\n"],"names":["z","isAndCriteria","isOrCriteria","isNotCriteria","InputError","createGetRule","AuthorizeResult","NotImplementedError","zodToJsonSchema","Router","express"],"mappings":"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"}
1
+ {"version":3,"file":"createPermissionIntegrationRouter.cjs.js","sources":["../../src/integration/createPermissionIntegrationRouter.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express, { Response } from 'express';\nimport Router from 'express-promise-router';\nimport { z } from 'zod';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport { InputError } from '@backstage/errors';\nimport {\n AuthorizeResult,\n DefinitivePolicyDecision,\n IdentifiedPermissionMessage,\n MetadataResponse as CommonMetadataResponse,\n MetadataResponseSerializedRule as CommonMetadataResponseSerializedRule,\n Permission,\n PermissionCondition,\n PermissionCriteria,\n PolicyDecision,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule, PermissionRuleset } from '../types';\nimport {\n NoInfer,\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\nimport { NotImplementedError } from '@backstage/errors';\nimport { PermissionResourceRef } from './createPermissionResourceRef';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z.union([\n z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }),\n z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }),\n z.object({ not: permissionCriteriaSchema }),\n z.object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n }),\n ]),\n);\n\nconst applyConditionsRequestSchema = z.object({\n items: z.array(\n z.object({\n id: z.string(),\n resourceRef: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ),\n});\n\n/**\n * A request to load the referenced resource and apply conditions in order to\n * finalize a conditional authorization response.\n *\n * @public\n */\nexport type ApplyConditionsRequestEntry = IdentifiedPermissionMessage<{\n resourceRef: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n}>;\n\n/**\n * A batch of {@link ApplyConditionsRequestEntry} objects.\n *\n * @public\n */\nexport type ApplyConditionsRequest = {\n items: ApplyConditionsRequestEntry[];\n};\n\n/**\n * The result of applying the conditions, expressed as a definitive authorize\n * result of ALLOW or DENY.\n *\n * @public\n */\nexport type ApplyConditionsResponseEntry =\n IdentifiedPermissionMessage<DefinitivePolicyDecision>;\n\n/**\n * A batch of {@link ApplyConditionsResponseEntry} objects.\n *\n * @public\n */\nexport type ApplyConditionsResponse = {\n items: ApplyConditionsResponseEntry[];\n};\n\n/**\n * Serialized permission rules, with the paramsSchema\n * converted from a ZodSchema to a JsonSchema.\n *\n * @public\n * @deprecated Please import from `@backstage/plugin-permission-common` instead.\n */\nexport type MetadataResponseSerializedRule =\n CommonMetadataResponseSerializedRule;\n\n/**\n * Response type for the .metadata endpoint.\n *\n * @public\n * @deprecated Please import from `@backstage/plugin-permission-common` instead.\n */\nexport type MetadataResponse = CommonMetadataResponse;\n\nconst applyConditions = <TResourceType extends string, TResource>(\n criteria: PermissionCriteria<PermissionCondition<TResourceType>>,\n resource: TResource | undefined,\n getRule: (name: string) => PermissionRule<TResource, unknown, TResourceType>,\n): boolean => {\n // If resource was not found, deny. This avoids leaking information from the\n // apply-conditions API which would allow a user to differentiate between\n // non-existent resources and resources to which they do not have access.\n if (resource === undefined) {\n return false;\n }\n\n if (isAndCriteria(criteria)) {\n return criteria.allOf.every(child =>\n applyConditions(child, resource, getRule),\n );\n } else if (isOrCriteria(criteria)) {\n return criteria.anyOf.some(child =>\n applyConditions(child, resource, getRule),\n );\n } else if (isNotCriteria(criteria)) {\n return !applyConditions(criteria.not, resource, getRule);\n }\n\n const rule = getRule(criteria.rule);\n const result = rule.paramsSchema?.safeParse(criteria.params);\n\n if (result && !result.success) {\n throw new InputError(`Parameters to rule are invalid`, result.error);\n }\n\n return rule.apply(resource, criteria.params ?? {});\n};\n\n/**\n * Takes some permission conditions and returns a definitive authorization result\n * on the resource to which they apply.\n *\n * @public\n */\nexport function createConditionAuthorizer<TResource>(\n permissionRuleset: PermissionRuleset<TResource>,\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean;\n/**\n * @public\n * @deprecated Use the version of `createConditionAuthorizer` that accepts a `PermissionRuleset` instead.\n */\nexport function createConditionAuthorizer<TResource, TQuery>(\n rules: PermissionRule<TResource, TQuery, string>[],\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean;\nexport function createConditionAuthorizer<TResource, TQuery>(\n rules:\n | PermissionRule<TResource, TQuery, string>[]\n | PermissionRuleset<TResource>,\n): (decision: PolicyDecision, resource: TResource | undefined) => boolean {\n const getRule =\n 'getRuleByName' in rules\n ? (n: string) => rules.getRuleByName(n)\n : createGetRule(rules);\n\n return (\n decision: PolicyDecision,\n resource: TResource | undefined,\n ): boolean => {\n if (decision.result === AuthorizeResult.CONDITIONAL) {\n return applyConditions(decision.conditions, resource, getRule);\n }\n\n return decision.result === AuthorizeResult.ALLOW;\n };\n}\n\n/**\n * Options for creating a permission integration router specific\n * for a particular resource type.\n *\n * @public\n * @deprecated {@link createPermissionIntegrationRouter} is deprecated\n */\nexport type CreatePermissionIntegrationRouterResourceOptions<\n TResourceType extends string,\n TResource,\n> = {\n resourceType: TResourceType;\n permissions?: Array<Permission>;\n // Do not infer value of TResourceType from supplied rules.\n // instead only consider the resourceType parameter, and\n // consider any rules whose resource type does not match\n // to be an error.\n rules: PermissionRule<TResource, any, NoInfer<TResourceType>>[];\n getResources?: (\n resourceRefs: string[],\n ) => Promise<Array<TResource | undefined>>;\n};\n\n/**\n * Options for creating a permission integration router exposing\n * permissions and rules from multiple resource types.\n *\n * @public\n * @deprecated {@link createPermissionIntegrationRouter} is deprecated\n */\nexport type PermissionIntegrationRouterOptions<\n TResourceType1 extends string = string,\n TResource1 = any,\n TResourceType2 extends string = string,\n TResource2 = any,\n TResourceType3 extends string = string,\n TResource3 = any,\n> = {\n resources: Readonly<\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n ]\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType2,\n TResource2\n >,\n ]\n | [\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType2,\n TResource2\n >,\n CreatePermissionIntegrationRouterResourceOptions<\n TResourceType3,\n TResource3\n >,\n ]\n >;\n};\n\nclass PermissionIntegrationMetadataStore {\n readonly #rulesByTypeByName = new Map<\n string,\n Map<string, PermissionRule<unknown, unknown, string>>\n >();\n readonly #permissionsByName = new Map<string, Permission>();\n readonly #resourcesByType = new Map<\n string,\n CreatePermissionIntegrationRouterResourceOptions<string, unknown>\n >();\n readonly #serializedRules = new Array<MetadataResponseSerializedRule>();\n\n getSerializedMetadata(): MetadataResponse {\n return {\n permissions: Array.from(this.#permissionsByName.values()),\n rules: this.#serializedRules,\n };\n }\n\n hasResourceType(type: string): boolean {\n return this.#resourcesByType.has(type);\n }\n\n async getResources(\n resourceType: string,\n refs: string[],\n ): Promise<Record<string, unknown>> {\n const resource = this.#resourcesByType.get(resourceType);\n if (!resource?.getResources) {\n throw new NotImplementedError(\n `This plugin does not expose any permission rule or can't evaluate the conditions request for ${resourceType}`,\n );\n }\n\n const uniqueRefs = Array.from(new Set(refs));\n const resources = await resource.getResources(uniqueRefs);\n return Object.fromEntries(\n uniqueRefs.map((ref, index) => [ref, resources[index]]),\n );\n }\n\n getRuleMapper(resourceType: string) {\n return (name: string): PermissionRule<unknown, unknown, string> => {\n const rule = this.#rulesByTypeByName.get(resourceType)?.get(name);\n if (!rule) {\n throw new Error(\n `Permission rule '${name}' does not exist for resource type '${resourceType}'`,\n );\n }\n return rule;\n };\n }\n\n addPermissions(permissions: Permission[]) {\n for (const permission of permissions) {\n // Permission naming conflicts are silently ignored\n this.#permissionsByName.set(permission.name, permission);\n }\n }\n\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]) {\n for (const rule of rules) {\n const rulesByName =\n this.#rulesByTypeByName.get(rule.resourceType) ?? new Map();\n this.#rulesByTypeByName.set(rule.resourceType, rulesByName);\n\n if (rulesByName.has(rule.name)) {\n throw new Error(\n `Refused to add permission rule for type '${rule.resourceType}' with name '${rule.name}' because it already exists`,\n );\n }\n rulesByName.set(rule.name, rule);\n\n this.#serializedRules.push({\n name: rule.name,\n description: rule.description,\n resourceType: rule.resourceType,\n paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})),\n });\n }\n }\n\n addResourceType(\n resource: CreatePermissionIntegrationRouterResourceOptions<string, unknown>,\n ) {\n const { resourceType } = resource;\n\n if (this.#resourcesByType.has(resourceType)) {\n throw new Error(\n `Refused to add permission resource with type '${resourceType}' because it already exists`,\n );\n }\n this.#resourcesByType.set(resourceType, resource);\n\n if (resource.rules) {\n this.addPermissionRules(resource.rules);\n }\n\n if (resource.permissions) {\n this.addPermissions(resource.permissions);\n }\n }\n}\n\n/**\n * Create an express Router which provides an authorization route to allow\n * integration between the permission backend and other Backstage backend\n * plugins. Plugin owners that wish to support conditional authorization for\n * their resources should add the router created by this function to their\n * express app inside their `createRouter` implementation.\n *\n * In case the `permissions` option is provided, the router also\n * provides a route that exposes permissions and routes of a plugin.\n *\n * In case resources is provided, the routes can handle permissions\n * for multiple resource types.\n *\n * @remarks\n *\n * To make this concrete, we can use the Backstage software catalog as an\n * example. The catalog has conditional rules around access to specific\n * _entities_ in the catalog. The _type_ of resource is captured here as\n * `resourceType`, a string identifier (`catalog-entity` in this example) that\n * can be provided with permission definitions. This is merely a _type_ to\n * verify that conditions in an authorization policy are constructed correctly,\n * not a reference to a specific resource.\n *\n * The `rules` parameter is an array of {@link PermissionRule}s that introduce\n * conditional filtering logic for resources; for the catalog, these are things\n * like `isEntityOwner` or `hasAnnotation`. Rules describe how to filter a list\n * of resources, and the `conditions` returned allow these rules to be applied\n * with specific parameters (such as 'group:default/team-a', or\n * 'backstage.io/edit-url').\n *\n * The `getResources` argument should load resources based on a reference\n * identifier. For the catalog, this is an\n * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be\n * any serialized format. This is used to construct the\n * `createPermissionIntegrationRouter`, a function to add an authorization route\n * to your backend plugin. This function will be called by the\n * `permission-backend` when authorization conditions relating to this plugin\n * need to be evaluated.\n *\n * @public\n * @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.\n */\nexport function createPermissionIntegrationRouter<\n TResourceType1 extends string,\n TResource1,\n TResourceType2 extends string,\n TResource2,\n TResourceType3 extends string,\n TResource3,\n>(\n options?:\n | { permissions: Array<Permission> }\n | CreatePermissionIntegrationRouterResourceOptions<\n TResourceType1,\n TResource1\n >\n | PermissionIntegrationRouterOptions<\n TResourceType1,\n TResource1,\n TResourceType2,\n TResource2,\n TResourceType3,\n TResource3\n >,\n): express.Router & {\n addPermissions(permissions: Permission[]): void;\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]): void;\n addResourceType<const TResourceType extends string, TResource>(\n resource: CreatePermissionIntegrationRouterResourceOptions<\n TResourceType,\n TResource\n >,\n ): void;\n getPermissionRuleset<TResource, TQuery, TResourceType extends string>(\n resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,\n ): PermissionRuleset<TResource, TQuery, TResourceType>;\n} {\n const store = new PermissionIntegrationMetadataStore();\n\n if (options) {\n if ('resources' in options) {\n // Not technically allowed by types, but it's historically been covered by tests\n if ('permissions' in options) {\n store.addPermissions(options.permissions as Permission[]);\n }\n\n for (const resource of options.resources) {\n store.addResourceType(resource);\n }\n } else if ('resourceType' in options) {\n store.addResourceType(options);\n } else {\n store.addPermissions(options.permissions);\n }\n }\n\n const router = Router();\n\n router.use('/.well-known/backstage/permissions/', express.json());\n\n router.get('/.well-known/backstage/permissions/metadata', (_, res) => {\n res.json(store.getSerializedMetadata());\n });\n\n router.post(\n '/.well-known/backstage/permissions/apply-conditions',\n async (req, res: Response<ApplyConditionsResponse | string>) => {\n const parseResult = applyConditionsRequestSchema.safeParse(req.body);\n if (!parseResult.success) {\n throw new InputError(parseResult.error.toString());\n }\n\n const { items: requests } = parseResult.data;\n\n const invalidResourceTypes = requests.filter(\n i => !store.hasResourceType(i.resourceType),\n );\n if (invalidResourceTypes.length) {\n throw new InputError(\n `Unexpected resource types: ${invalidResourceTypes\n .map(i => i.resourceType)\n .join(', ')}.`,\n );\n }\n\n const resourcesByType: Record<string, Record<string, any>> = {};\n for (const requestedType of new Set(requests.map(i => i.resourceType))) {\n resourcesByType[requestedType] = await store.getResources(\n requestedType,\n requests\n .filter(r => r.resourceType === requestedType)\n .map(i => i.resourceRef),\n );\n }\n\n res.json({\n items: requests.map(request => ({\n id: request.id,\n result: applyConditions(\n request.conditions,\n resourcesByType[request.resourceType][request.resourceRef],\n store.getRuleMapper(request.resourceType),\n )\n ? AuthorizeResult.ALLOW\n : AuthorizeResult.DENY,\n })),\n });\n },\n );\n\n return Object.assign(router, {\n addPermissions(permissions: Permission[]) {\n store.addPermissions(permissions);\n },\n addPermissionRules(rules: PermissionRule<unknown, unknown, string>[]) {\n store.addPermissionRules(rules);\n },\n addResourceType<const TResourceType extends string, TResource>(\n resource: CreatePermissionIntegrationRouterResourceOptions<\n TResourceType,\n TResource\n >,\n ) {\n store.addResourceType(resource);\n },\n getPermissionRuleset<TResource, TQuery, TResourceType extends string>(\n resourceRef: PermissionResourceRef<TResource, TQuery, TResourceType>,\n ): PermissionRuleset<TResource, TQuery, TResourceType> {\n return {\n getRuleByName: store.getRuleMapper(resourceRef.resourceType),\n } as PermissionRuleset<TResource, TQuery, TResourceType>;\n },\n });\n}\n"],"names":["z","isAndCriteria","isOrCriteria","isNotCriteria","InputError","createGetRule","AuthorizeResult","NotImplementedError","zodToJsonSchema","Router","express"],"mappings":";;;;;;;;;;;;;;;;AA2CA,MAAM,2BAEFA,KAAE,CAAA,IAAA;AAAA,EAAK,MACTA,MAAE,KAAM,CAAA;AAAA,IACNA,KAAA,CAAE,MAAO,CAAA,EAAE,KAAO,EAAAA,KAAA,CAAE,MAAM,wBAAwB,CAAA,CAAE,QAAS,EAAA,EAAG,CAAA;AAAA,IAChEA,KAAA,CAAE,MAAO,CAAA,EAAE,KAAO,EAAAA,KAAA,CAAE,MAAM,wBAAwB,CAAA,CAAE,QAAS,EAAA,EAAG,CAAA;AAAA,IAChEA,KAAE,CAAA,MAAA,CAAO,EAAE,GAAA,EAAK,0BAA0B,CAAA;AAAA,IAC1CA,MAAE,MAAO,CAAA;AAAA,MACP,IAAA,EAAMA,MAAE,MAAO,EAAA;AAAA,MACf,YAAA,EAAcA,MAAE,MAAO,EAAA;AAAA,MACvB,QAAQA,KAAE,CAAA,MAAA,CAAOA,MAAE,GAAI,EAAC,EAAE,QAAS;AAAA,KACpC;AAAA,GACF;AACH,CAAA;AAEA,MAAM,4BAAA,GAA+BA,MAAE,MAAO,CAAA;AAAA,EAC5C,OAAOA,KAAE,CAAA,KAAA;AAAA,IACPA,MAAE,MAAO,CAAA;AAAA,MACP,EAAA,EAAIA,MAAE,MAAO,EAAA;AAAA,MACb,WAAA,EAAaA,MAAE,MAAO,EAAA;AAAA,MACtB,YAAA,EAAcA,MAAE,MAAO,EAAA;AAAA,MACvB,UAAY,EAAA;AAAA,KACb;AAAA;AAEL,CAAC,CAAA;AA2DD,MAAM,eAAkB,GAAA,CACtB,QACA,EAAA,QAAA,EACA,OACY,KAAA;AAIZ,EAAA,IAAI,aAAa,KAAW,CAAA,EAAA;AAC1B,IAAO,OAAA,KAAA;AAAA;AAGT,EAAI,IAAAC,kBAAA,CAAc,QAAQ,CAAG,EAAA;AAC3B,IAAA,OAAO,SAAS,KAAM,CAAA,KAAA;AAAA,MAAM,CAC1B,KAAA,KAAA,eAAA,CAAgB,KAAO,EAAA,QAAA,EAAU,OAAO;AAAA,KAC1C;AAAA,GACF,MAAA,IAAWC,iBAAa,CAAA,QAAQ,CAAG,EAAA;AACjC,IAAA,OAAO,SAAS,KAAM,CAAA,IAAA;AAAA,MAAK,CACzB,KAAA,KAAA,eAAA,CAAgB,KAAO,EAAA,QAAA,EAAU,OAAO;AAAA,KAC1C;AAAA,GACF,MAAA,IAAWC,kBAAc,CAAA,QAAQ,CAAG,EAAA;AAClC,IAAA,OAAO,CAAC,eAAA,CAAgB,QAAS,CAAA,GAAA,EAAK,UAAU,OAAO,CAAA;AAAA;AAGzD,EAAM,MAAA,IAAA,GAAO,OAAQ,CAAA,QAAA,CAAS,IAAI,CAAA;AAClC,EAAA,MAAM,MAAS,GAAA,IAAA,CAAK,YAAc,EAAA,SAAA,CAAU,SAAS,MAAM,CAAA;AAE3D,EAAI,IAAA,MAAA,IAAU,CAAC,MAAA,CAAO,OAAS,EAAA;AAC7B,IAAA,MAAM,IAAIC,iBAAA,CAAW,CAAkC,8BAAA,CAAA,EAAA,MAAA,CAAO,KAAK,CAAA;AAAA;AAGrE,EAAA,OAAO,KAAK,KAAM,CAAA,QAAA,EAAU,QAAS,CAAA,MAAA,IAAU,EAAE,CAAA;AACnD,CAAA;AAkBO,SAAS,0BACd,KAGwE,EAAA;AACxE,EAAM,MAAA,OAAA,GACJ,eAAmB,IAAA,KAAA,GACf,CAAC,CAAA,KAAc,MAAM,aAAc,CAAA,CAAC,CACpC,GAAAC,kBAAA,CAAc,KAAK,CAAA;AAEzB,EAAO,OAAA,CACL,UACA,QACY,KAAA;AACZ,IAAI,IAAA,QAAA,CAAS,MAAW,KAAAC,sCAAA,CAAgB,WAAa,EAAA;AACnD,MAAA,OAAO,eAAgB,CAAA,QAAA,CAAS,UAAY,EAAA,QAAA,EAAU,OAAO,CAAA;AAAA;AAG/D,IAAO,OAAA,QAAA,CAAS,WAAWA,sCAAgB,CAAA,KAAA;AAAA,GAC7C;AACF;AA0EA,MAAM,kCAAmC,CAAA;AAAA,EAC9B,kBAAA,uBAAyB,GAGhC,EAAA;AAAA,EACO,kBAAA,uBAAyB,GAAwB,EAAA;AAAA,EACjD,gBAAA,uBAAuB,GAG9B,EAAA;AAAA,EACO,gBAAA,GAAmB,IAAI,KAAsC,EAAA;AAAA,EAEtE,qBAA0C,GAAA;AACxC,IAAO,OAAA;AAAA,MACL,aAAa,KAAM,CAAA,IAAA,CAAK,IAAK,CAAA,kBAAA,CAAmB,QAAQ,CAAA;AAAA,MACxD,OAAO,IAAK,CAAA;AAAA,KACd;AAAA;AACF,EAEA,gBAAgB,IAAuB,EAAA;AACrC,IAAO,OAAA,IAAA,CAAK,gBAAiB,CAAA,GAAA,CAAI,IAAI,CAAA;AAAA;AACvC,EAEA,MAAM,YACJ,CAAA,YAAA,EACA,IACkC,EAAA;AAClC,IAAA,MAAM,QAAW,GAAA,IAAA,CAAK,gBAAiB,CAAA,GAAA,CAAI,YAAY,CAAA;AACvD,IAAI,IAAA,CAAC,UAAU,YAAc,EAAA;AAC3B,MAAA,MAAM,IAAIC,0BAAA;AAAA,QACR,gGAAgG,YAAY,CAAA;AAAA,OAC9G;AAAA;AAGF,IAAA,MAAM,aAAa,KAAM,CAAA,IAAA,CAAK,IAAI,GAAA,CAAI,IAAI,CAAC,CAAA;AAC3C,IAAA,MAAM,SAAY,GAAA,MAAM,QAAS,CAAA,YAAA,CAAa,UAAU,CAAA;AACxD,IAAA,OAAO,MAAO,CAAA,WAAA;AAAA,MACZ,UAAA,CAAW,GAAI,CAAA,CAAC,GAAK,EAAA,KAAA,KAAU,CAAC,GAAK,EAAA,SAAA,CAAU,KAAK,CAAC,CAAC;AAAA,KACxD;AAAA;AACF,EAEA,cAAc,YAAsB,EAAA;AAClC,IAAA,OAAO,CAAC,IAA2D,KAAA;AACjE,MAAA,MAAM,OAAO,IAAK,CAAA,kBAAA,CAAmB,IAAI,YAAY,CAAA,EAAG,IAAI,IAAI,CAAA;AAChE,MAAA,IAAI,CAAC,IAAM,EAAA;AACT,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,iBAAA,EAAoB,IAAI,CAAA,oCAAA,EAAuC,YAAY,CAAA,CAAA;AAAA,SAC7E;AAAA;AAEF,MAAO,OAAA,IAAA;AAAA,KACT;AAAA;AACF,EAEA,eAAe,WAA2B,EAAA;AACxC,IAAA,KAAA,MAAW,cAAc,WAAa,EAAA;AAEpC,MAAA,IAAA,CAAK,kBAAmB,CAAA,GAAA,CAAI,UAAW,CAAA,IAAA,EAAM,UAAU,CAAA;AAAA;AACzD;AACF,EAEA,mBAAmB,KAAmD,EAAA;AACpE,IAAA,KAAA,MAAW,QAAQ,KAAO,EAAA;AACxB,MAAM,MAAA,WAAA,GACJ,KAAK,kBAAmB,CAAA,GAAA,CAAI,KAAK,YAAY,CAAA,wBAAS,GAAI,EAAA;AAC5D,MAAA,IAAA,CAAK,kBAAmB,CAAA,GAAA,CAAI,IAAK,CAAA,YAAA,EAAc,WAAW,CAAA;AAE1D,MAAA,IAAI,WAAY,CAAA,GAAA,CAAI,IAAK,CAAA,IAAI,CAAG,EAAA;AAC9B,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAA4C,yCAAA,EAAA,IAAA,CAAK,YAAY,CAAA,aAAA,EAAgB,KAAK,IAAI,CAAA,2BAAA;AAAA,SACxF;AAAA;AAEF,MAAY,WAAA,CAAA,GAAA,CAAI,IAAK,CAAA,IAAA,EAAM,IAAI,CAAA;AAE/B,MAAA,IAAA,CAAK,iBAAiB,IAAK,CAAA;AAAA,QACzB,MAAM,IAAK,CAAA,IAAA;AAAA,QACX,aAAa,IAAK,CAAA,WAAA;AAAA,QAClB,cAAc,IAAK,CAAA,YAAA;AAAA,QACnB,YAAA,EAAcC,iCAAgB,IAAK,CAAA,YAAA,IAAgBR,MAAE,MAAO,CAAA,EAAE,CAAC;AAAA,OAChE,CAAA;AAAA;AACH;AACF,EAEA,gBACE,QACA,EAAA;AACA,IAAM,MAAA,EAAE,cAAiB,GAAA,QAAA;AAEzB,IAAA,IAAI,IAAK,CAAA,gBAAA,CAAiB,GAAI,CAAA,YAAY,CAAG,EAAA;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,iDAAiD,YAAY,CAAA,2BAAA;AAAA,OAC/D;AAAA;AAEF,IAAK,IAAA,CAAA,gBAAA,CAAiB,GAAI,CAAA,YAAA,EAAc,QAAQ,CAAA;AAEhD,IAAA,IAAI,SAAS,KAAO,EAAA;AAClB,MAAK,IAAA,CAAA,kBAAA,CAAmB,SAAS,KAAK,CAAA;AAAA;AAGxC,IAAA,IAAI,SAAS,WAAa,EAAA;AACxB,MAAK,IAAA,CAAA,cAAA,CAAe,SAAS,WAAW,CAAA;AAAA;AAC1C;AAEJ;AA4CO,SAAS,kCAQd,OA0BA,EAAA;AACA,EAAM,MAAA,KAAA,GAAQ,IAAI,kCAAmC,EAAA;AAErD,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,IAAI,eAAe,OAAS,EAAA;AAE1B,MAAA,IAAI,iBAAiB,OAAS,EAAA;AAC5B,QAAM,KAAA,CAAA,cAAA,CAAe,QAAQ,WAA2B,CAAA;AAAA;AAG1D,MAAW,KAAA,MAAA,QAAA,IAAY,QAAQ,SAAW,EAAA;AACxC,QAAA,KAAA,CAAM,gBAAgB,QAAQ,CAAA;AAAA;AAChC,KACF,MAAA,IAAW,kBAAkB,OAAS,EAAA;AACpC,MAAA,KAAA,CAAM,gBAAgB,OAAO,CAAA;AAAA,KACxB,MAAA;AACL,MAAM,KAAA,CAAA,cAAA,CAAe,QAAQ,WAAW,CAAA;AAAA;AAC1C;AAGF,EAAA,MAAM,SAASS,uBAAO,EAAA;AAEtB,EAAA,MAAA,CAAO,GAAI,CAAA,qCAAA,EAAuCC,wBAAQ,CAAA,IAAA,EAAM,CAAA;AAEhE,EAAA,MAAA,CAAO,GAAI,CAAA,6CAAA,EAA+C,CAAC,CAAA,EAAG,GAAQ,KAAA;AACpE,IAAI,GAAA,CAAA,IAAA,CAAK,KAAM,CAAA,qBAAA,EAAuB,CAAA;AAAA,GACvC,CAAA;AAED,EAAO,MAAA,CAAA,IAAA;AAAA,IACL,qDAAA;AAAA,IACA,OAAO,KAAK,GAAoD,KAAA;AAC9D,MAAA,MAAM,WAAc,GAAA,4BAAA,CAA6B,SAAU,CAAA,GAAA,CAAI,IAAI,CAAA;AACnE,MAAI,IAAA,CAAC,YAAY,OAAS,EAAA;AACxB,QAAA,MAAM,IAAIN,iBAAA,CAAW,WAAY,CAAA,KAAA,CAAM,UAAU,CAAA;AAAA;AAGnD,MAAA,MAAM,EAAE,KAAA,EAAO,QAAS,EAAA,GAAI,WAAY,CAAA,IAAA;AAExC,MAAA,MAAM,uBAAuB,QAAS,CAAA,MAAA;AAAA,QACpC,CAAK,CAAA,KAAA,CAAC,KAAM,CAAA,eAAA,CAAgB,EAAE,YAAY;AAAA,OAC5C;AACA,MAAA,IAAI,qBAAqB,MAAQ,EAAA;AAC/B,QAAA,MAAM,IAAIA,iBAAA;AAAA,UACR,CAAA,2BAAA,EAA8B,qBAC3B,GAAI,CAAA,CAAA,CAAA,KAAK,EAAE,YAAY,CAAA,CACvB,IAAK,CAAA,IAAI,CAAC,CAAA,CAAA;AAAA,SACf;AAAA;AAGF,MAAA,MAAM,kBAAuD,EAAC;AAC9D,MAAW,KAAA,MAAA,aAAA,IAAiB,IAAI,GAAI,CAAA,QAAA,CAAS,IAAI,CAAK,CAAA,KAAA,CAAA,CAAE,YAAY,CAAC,CAAG,EAAA;AACtE,QAAgB,eAAA,CAAA,aAAa,CAAI,GAAA,MAAM,KAAM,CAAA,YAAA;AAAA,UAC3C,aAAA;AAAA,UACA,QAAA,CACG,MAAO,CAAA,CAAA,CAAA,KAAK,CAAE,CAAA,YAAA,KAAiB,aAAa,CAC5C,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,WAAW;AAAA,SAC3B;AAAA;AAGF,MAAA,GAAA,CAAI,IAAK,CAAA;AAAA,QACP,KAAA,EAAO,QAAS,CAAA,GAAA,CAAI,CAAY,OAAA,MAAA;AAAA,UAC9B,IAAI,OAAQ,CAAA,EAAA;AAAA,UACZ,MAAQ,EAAA,eAAA;AAAA,YACN,OAAQ,CAAA,UAAA;AAAA,YACR,eAAgB,CAAA,OAAA,CAAQ,YAAY,CAAA,CAAE,QAAQ,WAAW,CAAA;AAAA,YACzD,KAAA,CAAM,aAAc,CAAA,OAAA,CAAQ,YAAY;AAAA,WAC1C,GACIE,sCAAgB,CAAA,KAAA,GAChBA,sCAAgB,CAAA;AAAA,SACpB,CAAA;AAAA,OACH,CAAA;AAAA;AACH,GACF;AAEA,EAAO,OAAA,MAAA,CAAO,OAAO,MAAQ,EAAA;AAAA,IAC3B,eAAe,WAA2B,EAAA;AACxC,MAAA,KAAA,CAAM,eAAe,WAAW,CAAA;AAAA,KAClC;AAAA,IACA,mBAAmB,KAAmD,EAAA;AACpE,MAAA,KAAA,CAAM,mBAAmB,KAAK,CAAA;AAAA,KAChC;AAAA,IACA,gBACE,QAIA,EAAA;AACA,MAAA,KAAA,CAAM,gBAAgB,QAAQ,CAAA;AAAA,KAChC;AAAA,IACA,qBACE,WACqD,EAAA;AACrD,MAAO,OAAA;AAAA,QACL,aAAe,EAAA,KAAA,CAAM,aAAc,CAAA,WAAA,CAAY,YAAY;AAAA,OAC7D;AAAA;AACF,GACD,CAAA;AACH;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-permission-node",
3
- "version": "0.8.9-next.1",
3
+ "version": "0.9.0",
4
4
  "description": "Common permission and authorization utilities for backend plugins",
5
5
  "backstage": {
6
6
  "role": "node-library",
@@ -43,7 +43,7 @@
43
43
  "types": "./dist/index.d.ts",
44
44
  "typesVersions": {
45
45
  "*": {
46
- "index": [
46
+ "*": [
47
47
  "dist/index.d.ts"
48
48
  ],
49
49
  "alpha": [
@@ -64,12 +64,11 @@
64
64
  "test": "backstage-cli package test"
65
65
  },
66
66
  "dependencies": {
67
- "@backstage/backend-common": "^0.25.0",
68
- "@backstage/backend-plugin-api": "1.2.1-next.1",
69
- "@backstage/config": "1.3.2",
70
- "@backstage/errors": "1.2.7",
71
- "@backstage/plugin-auth-node": "0.6.1-next.1",
72
- "@backstage/plugin-permission-common": "0.8.4",
67
+ "@backstage/backend-plugin-api": "^1.2.1",
68
+ "@backstage/config": "^1.3.2",
69
+ "@backstage/errors": "^1.2.7",
70
+ "@backstage/plugin-auth-node": "^0.6.1",
71
+ "@backstage/plugin-permission-common": "^0.8.4",
73
72
  "@types/express": "^4.17.6",
74
73
  "express": "^4.17.1",
75
74
  "express-promise-router": "^4.1.0",
@@ -77,9 +76,9 @@
77
76
  "zod-to-json-schema": "^3.20.4"
78
77
  },
79
78
  "devDependencies": {
80
- "@backstage/backend-defaults": "0.8.2-next.1",
81
- "@backstage/backend-test-utils": "1.3.1-next.1",
82
- "@backstage/cli": "0.30.1-next.0",
79
+ "@backstage/backend-defaults": "^0.8.2",
80
+ "@backstage/backend-test-utils": "^1.3.1",
81
+ "@backstage/cli": "^0.31.0",
83
82
  "@types/supertest": "^2.0.8",
84
83
  "msw": "^1.0.0",
85
84
  "supertest": "^7.0.0"