@backstage/plugin-permission-node 0.1.0 → 0.2.3

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,50 @@
1
1
  # @backstage/plugin-permission-node
2
2
 
3
+ ## 0.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/plugin-auth-backend@0.6.0
9
+ - @backstage/backend-common@0.10.1
10
+
11
+ ## 0.2.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 2f8a9b665f: Add `ServerPermissionClient`, which implements `PermissionAuthorizer` from @backstage/plugin-permission-common. This implementation skips authorization entirely when the supplied token is a valid backend-to-backend token, thereby allowing backend-to-backend systems to communicate without authorization.
16
+
17
+ The `ServerPermissionClient` should always be used over the standard `PermissionClient` in plugin backends.
18
+
19
+ - Updated dependencies
20
+ - @backstage/backend-common@0.10.0
21
+ - @backstage/plugin-auth-backend@0.5.2
22
+ - @backstage/plugin-permission-common@0.3.0
23
+
24
+ ## 0.2.1
25
+
26
+ ### Patch Changes
27
+
28
+ - dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly
29
+ - a036b65c2f: Updated to use the new `BackstageIdentityResponse` type from `@backstage/plugin-auth-backend`.
30
+
31
+ The `BackstageIdentityResponse` type is backwards compatible with the `BackstageIdentity`, and provides an additional `identity` field with the claims of the user.
32
+
33
+ - Updated dependencies
34
+ - @backstage/plugin-auth-backend@0.5.0
35
+
36
+ ## 0.2.0
37
+
38
+ ### Minor Changes
39
+
40
+ - e7851efa9e: Rename and adjust permission policy return type to reduce nesting
41
+ - 450ca92330: Change route used for integration between the authorization framework and other plugin backends to use the /.well-known prefix.
42
+
43
+ ### Patch Changes
44
+
45
+ - Updated dependencies
46
+ - @backstage/plugin-auth-backend@0.4.10
47
+
3
48
  ## 0.1.0
4
49
 
5
50
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
5
6
  var express = require('express');
6
7
  var zod = require('zod');
7
- var pluginPermissionCommon = require('@backstage/plugin-permission-common');
8
8
 
9
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
10
 
@@ -16,13 +16,14 @@ const createConditionFactory = (rule) => (...params) => ({
16
16
  });
17
17
 
18
18
  const createConditionExports = (options) => {
19
- const {pluginId, resourceType, rules} = options;
19
+ const { pluginId, resourceType, rules } = options;
20
20
  return {
21
21
  conditions: Object.entries(rules).reduce((acc, [key, rule]) => ({
22
22
  ...acc,
23
23
  [key]: createConditionFactory(rule)
24
24
  }), {}),
25
- createConditions: (conditions) => ({
25
+ createPolicyDecision: (conditions) => ({
26
+ result: pluginPermissionCommon.AuthorizeResult.CONDITIONAL,
26
27
  pluginId,
27
28
  resourceType,
28
29
  conditions
@@ -66,9 +67,9 @@ const createConditionTransformer = (permissionRules) => {
66
67
  };
67
68
 
68
69
  const permissionCriteriaSchema = zod.z.lazy(() => zod.z.union([
69
- zod.z.object({anyOf: zod.z.array(permissionCriteriaSchema)}),
70
- zod.z.object({allOf: zod.z.array(permissionCriteriaSchema)}),
71
- zod.z.object({not: permissionCriteriaSchema}),
70
+ zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema) }),
71
+ zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema) }),
72
+ zod.z.object({ not: permissionCriteriaSchema }),
72
73
  zod.z.object({
73
74
  rule: zod.z.string(),
74
75
  params: zod.z.array(zod.z.unknown())
@@ -89,19 +90,16 @@ const applyConditions = (criteria, resource, getRule) => {
89
90
  }
90
91
  return getRule(criteria.rule).apply(resource, ...criteria.params);
91
92
  };
92
- const createPermissionIntegrationRouter = ({
93
- resourceType,
94
- rules,
95
- getResource
96
- }) => {
93
+ const createPermissionIntegrationRouter = (options) => {
94
+ const { resourceType, rules, getResource } = options;
97
95
  const router = express.Router();
98
96
  const getRule = createGetRule(rules);
99
- router.post("/permissions/apply-conditions", express__default['default'].json(), async (req, res) => {
97
+ router.post("/.well-known/backstage/permissions/apply-conditions", express__default["default"].json(), async (req, res) => {
100
98
  const parseResult = applyConditionsRequestSchema.safeParse(req.body);
101
99
  if (!parseResult.success) {
102
100
  return res.status(400).send(`Invalid request body.`);
103
101
  }
104
- const {data: body} = parseResult;
102
+ const { data: body } = parseResult;
105
103
  if (body.resourceType !== resourceType) {
106
104
  return res.status(400).send(`Unexpected resource type: ${body.resourceType}.`);
107
105
  }
@@ -116,6 +114,41 @@ const createPermissionIntegrationRouter = ({
116
114
  return router;
117
115
  };
118
116
 
117
+ class ServerPermissionClient {
118
+ static fromConfig(config, options) {
119
+ var _a;
120
+ const { discovery, tokenManager } = options;
121
+ const permissionClient = new pluginPermissionCommon.PermissionClient({ discovery, config });
122
+ const permissionEnabled = (_a = config.getOptionalBoolean("permission.enabled")) != null ? _a : false;
123
+ if (permissionEnabled && tokenManager.isInsecureServerTokenManager) {
124
+ throw new Error("You must configure at least one key in backend.auth.keys if permissions are enabled.");
125
+ }
126
+ return new ServerPermissionClient({
127
+ permissionClient,
128
+ tokenManager,
129
+ permissionEnabled
130
+ });
131
+ }
132
+ constructor(options) {
133
+ this.permissionClient = options.permissionClient;
134
+ this.tokenManager = options.tokenManager;
135
+ this.permissionEnabled = options.permissionEnabled;
136
+ }
137
+ async authorize(requests, options) {
138
+ if (!this.permissionEnabled || await this.isValidServerToken(options == null ? void 0 : options.token)) {
139
+ return requests.map((_) => ({ result: pluginPermissionCommon.AuthorizeResult.ALLOW }));
140
+ }
141
+ return this.permissionClient.authorize(requests, options);
142
+ }
143
+ async isValidServerToken(token) {
144
+ if (!token) {
145
+ return false;
146
+ }
147
+ return this.tokenManager.authenticate(token).then(() => true).catch(() => false);
148
+ }
149
+ }
150
+
151
+ exports.ServerPermissionClient = ServerPermissionClient;
119
152
  exports.createConditionExports = createConditionExports;
120
153
  exports.createConditionFactory = createConditionFactory;
121
154
  exports.createConditionTransformer = createConditionTransformer;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/integration/createConditionFactory.ts","../src/integration/createConditionExports.ts","../src/integration/util.ts","../src/integration/createConditionTransformer.ts","../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 { PermissionRule } from '../types';\n\n/**\n * Creates a condition factory function for a given authorization rule and parameter types.\n *\n * @remarks\n *\n * For example, an isEntityOwner rule for catalog entities might take an array of entityRef strings.\n * The rule itself defines _how_ to check a given resource, whereas a condition also includes _what_\n * to verify.\n *\n * Plugin authors should generally use the {@link createConditionExports} in order to efficiently\n * create multiple condition factories. This helper should generally only be used to construct\n * condition factories for third-party rules that aren't part of the backend plugin with which\n * they're intended to integrate.\n *\n * @public\n */\nexport const createConditionFactory =\n <TParams extends any[]>(rule: PermissionRule<unknown, unknown, TParams>) =>\n (...params: TParams) => ({\n rule: rule.name,\n params,\n });\n","/*\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 PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\nimport { createConditionFactory } from './createConditionFactory';\n\n/**\n * A utility type for mapping a single {@link PermissionRule} to its\n * corresponding {@link @backstage/plugin-permission-common#PermissionCondition}.\n *\n * @public\n */\nexport type Condition<TRule> = TRule extends PermissionRule<\n any,\n any,\n infer TParams\n>\n ? (...params: TParams) => PermissionCondition<TParams>\n : never;\n\n/**\n * A utility type for mapping {@link PermissionRule}s to their corresponding\n * {@link @backstage/plugin-permission-common#PermissionCondition}s.\n *\n * @public\n */\nexport type Conditions<\n TRules extends Record<string, PermissionRule<any, any>>,\n> = {\n [Name in keyof TRules]: Condition<TRules[Name]>;\n};\n\n/**\n * Creates the recommended condition-related exports for a given plugin based on the built-in\n * {@link PermissionRule}s it supports.\n *\n * @remarks\n *\n * The function returns a `conditions` object containing a\n * {@link @backstage/plugin-permission-common#PermissionCondition} factory for each of the\n * supplied {@link PermissionRule}s, along with a `createConditions` function which builds the\n * wrapper object needed to enclose conditions when authoring {@link PermissionPolicy} implementations.\n *\n * Plugin authors should generally call this method with all the built-in {@link PermissionRule}s\n * the plugin supports, and export the resulting `conditions` object and `createConditions`\n * function so that they can be used by {@link PermissionPolicy} authors.\n *\n * @public\n */\nexport const createConditionExports = <\n TResource,\n TRules extends Record<string, PermissionRule<TResource, any>>,\n>(options: {\n pluginId: string;\n resourceType: string;\n rules: TRules;\n}): {\n conditions: Conditions<TRules>;\n createConditions: (conditions: PermissionCriteria<PermissionCondition>) => {\n pluginId: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n };\n} => {\n const { pluginId, resourceType, rules } = options;\n\n return {\n conditions: Object.entries(rules).reduce(\n (acc, [key, rule]) => ({\n ...acc,\n [key]: createConditionFactory(rule),\n }),\n {} as Conditions<TRules>,\n ),\n createConditions: (\n conditions: PermissionCriteria<PermissionCondition>,\n ) => ({\n pluginId,\n resourceType,\n conditions,\n }),\n };\n};\n","/*\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 { PermissionCriteria } from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\n\nexport const isAndCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { allOf: PermissionCriteria<unknown>[] } =>\n Object.prototype.hasOwnProperty.call(filter, 'allOf');\n\nexport const isOrCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { anyOf: PermissionCriteria<unknown>[] } =>\n Object.prototype.hasOwnProperty.call(filter, 'anyOf');\n\nexport const isNotCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { not: PermissionCriteria<unknown> } =>\n Object.prototype.hasOwnProperty.call(filter, 'not');\n\nexport const createGetRule = <TResource, TQuery>(\n rules: PermissionRule<TResource, TQuery>[],\n) => {\n const rulesMap = new Map(Object.values(rules).map(rule => [rule.name, rule]));\n\n return (name: string): PermissionRule<TResource, TQuery> => {\n const rule = rulesMap.get(name);\n\n if (!rule) {\n throw new Error(`Unexpected permission rule: ${name}`);\n }\n\n return rule;\n };\n};\n","/*\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 */\nimport {\n PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\nimport {\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\n\nconst mapConditions = <TQuery>(\n criteria: PermissionCriteria<PermissionCondition>,\n getRule: (name: string) => PermissionRule<unknown, TQuery>,\n): PermissionCriteria<TQuery> => {\n if (isAndCriteria(criteria)) {\n return {\n allOf: criteria.allOf.map(child => mapConditions(child, getRule)),\n };\n } else if (isOrCriteria(criteria)) {\n return {\n anyOf: criteria.anyOf.map(child => mapConditions(child, getRule)),\n };\n } else if (isNotCriteria(criteria)) {\n return {\n not: mapConditions(criteria.not, getRule),\n };\n }\n\n return getRule(criteria.rule).toQuery(...criteria.params);\n};\n\n/**\n * A function which accepts {@link @backstage/plugin-permission-common#PermissionCondition}s\n * logically grouped in a {@link @backstage/plugin-permission-common#PermissionCriteria}\n * object, and transforms the {@link @backstage/plugin-permission-common#PermissionCondition}s\n * into plugin specific query fragments while retaining the enclosing criteria shape.\n *\n * @public\n */\nexport type ConditionTransformer<TQuery> = (\n conditions: PermissionCriteria<PermissionCondition>,\n) => PermissionCriteria<TQuery>;\n\n/**\n * A higher-order helper function which accepts an array of\n * {@link PermissionRule}s, and returns a {@link ConditionTransformer}\n * which transforms input conditions into equivalent plugin-specific\n * query fragments using the supplied rules.\n *\n * @public\n */\nexport const createConditionTransformer = <\n TQuery,\n TRules extends PermissionRule<any, TQuery>[],\n>(\n permissionRules: [...TRules],\n): ConditionTransformer<TQuery> => {\n const getRule = createGetRule(permissionRules);\n\n return conditions => mapConditions(conditions, getRule);\n};\n","/*\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, Router } from 'express';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\nimport {\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z.union([\n z.object({ anyOf: z.array(permissionCriteriaSchema) }),\n z.object({ allOf: z.array(permissionCriteriaSchema) }),\n z.object({ not: permissionCriteriaSchema }),\n z.object({\n rule: z.string(),\n params: z.array(z.unknown()),\n }),\n ]),\n);\n\nconst applyConditionsRequestSchema = z.object({\n resourceRef: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\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 ApplyConditionsRequest = {\n resourceRef: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\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 ApplyConditionsResponse = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\nconst applyConditions = <TResource>(\n criteria: PermissionCriteria<PermissionCondition>,\n resource: TResource,\n getRule: (name: string) => PermissionRule<TResource, unknown>,\n): boolean => {\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 return getRule(criteria.rule).apply(resource, ...criteria.params);\n};\n\n/**\n * Create an express Router which provides an authorization route to allow integration between the\n * permission backend and other Backstage backend plugins. Plugin owners that wish to support\n * conditional authorization for their resources should add the router created by this function\n * to their express app inside their `createRouter` implementation.\n *\n * @remarks\n *\n * To make this concrete, we can use the Backstage software catalog as an example. The catalog has\n * conditional rules around access to specific _entities_ in the catalog. The _type_ of resource is\n * captured here as `resourceType`, a string identifier (`catalog-entity` in this example) that can\n * be provided with permission definitions. This is merely a _type_ to verify that conditions in an\n * authorization policy are constructed correctly, not a reference to a specific resource.\n *\n * The `rules` parameter is an array of {@link PermissionRule}s that introduce conditional\n * filtering logic for resources; for the catalog, these are things like `isEntityOwner` or\n * `hasAnnotation`. Rules describe how to filter a list of resources, and the `conditions` returned\n * allow these rules to be applied with specific parameters (such as 'group:default/team-a', or\n * 'backstage.io/edit-url').\n *\n * The `getResource` argument should load a resource by reference. For the catalog, this is an\n * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be any serialized format.\n * This is used to construct the `createPermissionIntegrationRouter`, a function to add an\n * authorization route to your backend plugin. This route will be called by the `permission-backend`\n * when authorization conditions relating to this plugin need to be evaluated.\n * @public\n */\nexport const createPermissionIntegrationRouter = <TResource>({\n resourceType,\n rules,\n getResource,\n}: {\n resourceType: string;\n rules: PermissionRule<TResource, any>[];\n getResource: (resourceRef: string) => Promise<TResource | undefined>;\n}): Router => {\n const router = Router();\n\n const getRule = createGetRule(rules);\n\n router.post(\n '/permissions/apply-conditions',\n express.json(),\n async (\n req,\n res: Response<\n | {\n result: Omit<AuthorizeResult, AuthorizeResult.CONDITIONAL>;\n }\n | string\n >,\n ) => {\n const parseResult = applyConditionsRequestSchema.safeParse(req.body);\n\n if (!parseResult.success) {\n return res.status(400).send(`Invalid request body.`);\n }\n\n const { data: body } = parseResult;\n\n if (body.resourceType !== resourceType) {\n return res\n .status(400)\n .send(`Unexpected resource type: ${body.resourceType}.`);\n }\n\n const resource = await getResource(body.resourceRef);\n\n if (!resource) {\n return res\n .status(400)\n .send(`Resource for ref ${body.resourceRef} not found.`);\n }\n\n return res.status(200).json({\n result: applyConditions(body.conditions, resource, getRule)\n ? AuthorizeResult.ALLOW\n : AuthorizeResult.DENY,\n });\n },\n );\n\n return router;\n};\n"],"names":["z","Router","express","AuthorizeResult"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/integration/createConditionFactory.ts","../src/integration/createConditionExports.ts","../src/integration/util.ts","../src/integration/createConditionTransformer.ts","../src/integration/createPermissionIntegrationRouter.ts","../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 { PermissionRule } from '../types';\n\n/**\n * Creates a condition factory function for a given authorization rule and parameter types.\n *\n * @remarks\n *\n * For example, an isEntityOwner rule for catalog entities might take an array of entityRef strings.\n * The rule itself defines _how_ to check a given resource, whereas a condition also includes _what_\n * to verify.\n *\n * Plugin authors should generally use the {@link createConditionExports} in order to efficiently\n * create multiple condition factories. This helper should generally only be used to construct\n * condition factories for third-party rules that aren't part of the backend plugin with which\n * they're intended to integrate.\n *\n * @public\n */\nexport const createConditionFactory =\n <TParams extends any[]>(rule: PermissionRule<unknown, unknown, TParams>) =>\n (...params: TParams) => ({\n rule: rule.name,\n params,\n });\n","/*\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 AuthorizeResult,\n PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { ConditionalPolicyDecision } from '../policy';\nimport { PermissionRule } from '../types';\nimport { createConditionFactory } from './createConditionFactory';\n\n/**\n * A utility type for mapping a single {@link PermissionRule} to its\n * corresponding {@link @backstage/plugin-permission-common#PermissionCondition}.\n *\n * @public\n */\nexport type Condition<TRule> = TRule extends PermissionRule<\n any,\n any,\n infer TParams\n>\n ? (...params: TParams) => PermissionCondition<TParams>\n : never;\n\n/**\n * A utility type for mapping {@link PermissionRule}s to their corresponding\n * {@link @backstage/plugin-permission-common#PermissionCondition}s.\n *\n * @public\n */\nexport type Conditions<\n TRules extends Record<string, PermissionRule<any, any>>,\n> = {\n [Name in keyof TRules]: Condition<TRules[Name]>;\n};\n\n/**\n * Creates the recommended condition-related exports for a given plugin based on the built-in\n * {@link PermissionRule}s it supports.\n *\n * @remarks\n *\n * The function returns a `conditions` object containing a\n * {@link @backstage/plugin-permission-common#PermissionCondition} factory for each of the\n * supplied {@link PermissionRule}s, along with a `createConditions` function which builds the\n * wrapper object needed to enclose conditions when authoring {@link PermissionPolicy} implementations.\n *\n * Plugin authors should generally call this method with all the built-in {@link PermissionRule}s\n * the plugin supports, and export the resulting `conditions` object and `createConditions`\n * function so that they can be used by {@link PermissionPolicy} authors.\n *\n * @public\n */\nexport const createConditionExports = <\n TResource,\n TRules extends Record<string, PermissionRule<TResource, any>>,\n>(options: {\n pluginId: string;\n resourceType: string;\n rules: TRules;\n}): {\n conditions: Conditions<TRules>;\n createPolicyDecision: (\n conditions: PermissionCriteria<PermissionCondition>,\n ) => ConditionalPolicyDecision;\n} => {\n const { pluginId, resourceType, rules } = options;\n\n return {\n conditions: Object.entries(rules).reduce(\n (acc, [key, rule]) => ({\n ...acc,\n [key]: createConditionFactory(rule),\n }),\n {} as Conditions<TRules>,\n ),\n createPolicyDecision: (\n conditions: PermissionCriteria<PermissionCondition>,\n ) => ({\n result: AuthorizeResult.CONDITIONAL,\n pluginId,\n resourceType,\n conditions,\n }),\n };\n};\n","/*\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 { PermissionCriteria } from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\n\nexport const isAndCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { allOf: PermissionCriteria<unknown>[] } =>\n Object.prototype.hasOwnProperty.call(filter, 'allOf');\n\nexport const isOrCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { anyOf: PermissionCriteria<unknown>[] } =>\n Object.prototype.hasOwnProperty.call(filter, 'anyOf');\n\nexport const isNotCriteria = (\n filter: PermissionCriteria<unknown>,\n): filter is { not: PermissionCriteria<unknown> } =>\n Object.prototype.hasOwnProperty.call(filter, 'not');\n\nexport const createGetRule = <TResource, TQuery>(\n rules: PermissionRule<TResource, TQuery>[],\n) => {\n const rulesMap = new Map(Object.values(rules).map(rule => [rule.name, rule]));\n\n return (name: string): PermissionRule<TResource, TQuery> => {\n const rule = rulesMap.get(name);\n\n if (!rule) {\n throw new Error(`Unexpected permission rule: ${name}`);\n }\n\n return rule;\n };\n};\n","/*\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 */\nimport {\n PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\nimport {\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\n\nconst mapConditions = <TQuery>(\n criteria: PermissionCriteria<PermissionCondition>,\n getRule: (name: string) => PermissionRule<unknown, TQuery>,\n): PermissionCriteria<TQuery> => {\n if (isAndCriteria(criteria)) {\n return {\n allOf: criteria.allOf.map(child => mapConditions(child, getRule)),\n };\n } else if (isOrCriteria(criteria)) {\n return {\n anyOf: criteria.anyOf.map(child => mapConditions(child, getRule)),\n };\n } else if (isNotCriteria(criteria)) {\n return {\n not: mapConditions(criteria.not, getRule),\n };\n }\n\n return getRule(criteria.rule).toQuery(...criteria.params);\n};\n\n/**\n * A function which accepts {@link @backstage/plugin-permission-common#PermissionCondition}s\n * logically grouped in a {@link @backstage/plugin-permission-common#PermissionCriteria}\n * object, and transforms the {@link @backstage/plugin-permission-common#PermissionCondition}s\n * into plugin specific query fragments while retaining the enclosing criteria shape.\n *\n * @public\n */\nexport type ConditionTransformer<TQuery> = (\n conditions: PermissionCriteria<PermissionCondition>,\n) => PermissionCriteria<TQuery>;\n\n/**\n * A higher-order helper function which accepts an array of\n * {@link PermissionRule}s, and returns a {@link ConditionTransformer}\n * which transforms input conditions into equivalent plugin-specific\n * query fragments using the supplied rules.\n *\n * @public\n */\nexport const createConditionTransformer = <\n TQuery,\n TRules extends PermissionRule<any, TQuery>[],\n>(\n permissionRules: [...TRules],\n): ConditionTransformer<TQuery> => {\n const getRule = createGetRule(permissionRules);\n\n return conditions => mapConditions(conditions, getRule);\n};\n","/*\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, Router } from 'express';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionCondition,\n PermissionCriteria,\n} from '@backstage/plugin-permission-common';\nimport { PermissionRule } from '../types';\nimport {\n createGetRule,\n isAndCriteria,\n isNotCriteria,\n isOrCriteria,\n} from './util';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z.union([\n z.object({ anyOf: z.array(permissionCriteriaSchema) }),\n z.object({ allOf: z.array(permissionCriteriaSchema) }),\n z.object({ not: permissionCriteriaSchema }),\n z.object({\n rule: z.string(),\n params: z.array(z.unknown()),\n }),\n ]),\n);\n\nconst applyConditionsRequestSchema = z.object({\n resourceRef: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\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 ApplyConditionsRequest = {\n resourceRef: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\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 ApplyConditionsResponse = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\nconst applyConditions = <TResource>(\n criteria: PermissionCriteria<PermissionCondition>,\n resource: TResource,\n getRule: (name: string) => PermissionRule<TResource, unknown>,\n): boolean => {\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 return getRule(criteria.rule).apply(resource, ...criteria.params);\n};\n\n/**\n * Create an express Router which provides an authorization route to allow integration between the\n * permission backend and other Backstage backend plugins. Plugin owners that wish to support\n * conditional authorization for their resources should add the router created by this function\n * to their express app inside their `createRouter` implementation.\n *\n * @remarks\n *\n * To make this concrete, we can use the Backstage software catalog as an example. The catalog has\n * conditional rules around access to specific _entities_ in the catalog. The _type_ of resource is\n * captured here as `resourceType`, a string identifier (`catalog-entity` in this example) that can\n * be provided with permission definitions. This is merely a _type_ to verify that conditions in an\n * authorization policy are constructed correctly, not a reference to a specific resource.\n *\n * The `rules` parameter is an array of {@link PermissionRule}s that introduce conditional\n * filtering logic for resources; for the catalog, these are things like `isEntityOwner` or\n * `hasAnnotation`. Rules describe how to filter a list of resources, and the `conditions` returned\n * allow these rules to be applied with specific parameters (such as 'group:default/team-a', or\n * 'backstage.io/edit-url').\n *\n * The `getResource` argument should load a resource by reference. For the catalog, this is an\n * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be any serialized format.\n * This is used to construct the `createPermissionIntegrationRouter`, a function to add an\n * authorization route to your backend plugin. This route will be called by the `permission-backend`\n * when authorization conditions relating to this plugin need to be evaluated.\n *\n * @public\n */\nexport const createPermissionIntegrationRouter = <TResource>(options: {\n resourceType: string;\n rules: PermissionRule<TResource, any>[];\n getResource: (resourceRef: string) => Promise<TResource | undefined>;\n}): Router => {\n const { resourceType, rules, getResource } = options;\n const router = Router();\n\n const getRule = createGetRule(rules);\n\n router.post(\n '/.well-known/backstage/permissions/apply-conditions',\n express.json(),\n async (\n req,\n res: Response<\n | {\n result: Omit<AuthorizeResult, AuthorizeResult.CONDITIONAL>;\n }\n | string\n >,\n ) => {\n const parseResult = applyConditionsRequestSchema.safeParse(req.body);\n\n if (!parseResult.success) {\n return res.status(400).send(`Invalid request body.`);\n }\n\n const { data: body } = parseResult;\n\n if (body.resourceType !== resourceType) {\n return res\n .status(400)\n .send(`Unexpected resource type: ${body.resourceType}.`);\n }\n\n const resource = await getResource(body.resourceRef);\n\n if (!resource) {\n return res\n .status(400)\n .send(`Resource for ref ${body.resourceRef} not found.`);\n }\n\n return res.status(200).json({\n result: applyConditions(body.conditions, resource, getRule)\n ? AuthorizeResult.ALLOW\n : AuthorizeResult.DENY,\n });\n },\n );\n\n return router;\n};\n","/*\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 PluginEndpointDiscovery,\n} from '@backstage/backend-common';\nimport { Config } from '@backstage/config';\nimport {\n AuthorizeRequest,\n AuthorizeRequestOptions,\n AuthorizeResponse,\n AuthorizeResult,\n PermissionClient,\n PermissionAuthorizer,\n} from '@backstage/plugin-permission-common';\n\n/**\n * A thin wrapper around\n * {@link @backstage/plugin-permission-common#PermissionClient} that allows all\n * backend-to-backend requests.\n * @public\n */\nexport class ServerPermissionClient implements PermissionAuthorizer {\n private readonly permissionClient: PermissionClient;\n private readonly tokenManager: TokenManager;\n private readonly permissionEnabled: boolean;\n\n static fromConfig(\n config: Config,\n options: {\n discovery: PluginEndpointDiscovery;\n tokenManager: TokenManager;\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 as any).isInsecureServerTokenManager\n ) {\n throw new Error(\n 'You must configure at least one key in backend.auth.keys if permissions are enabled.',\n );\n }\n\n return new ServerPermissionClient({\n permissionClient,\n tokenManager,\n permissionEnabled,\n });\n }\n\n private constructor(options: {\n permissionClient: PermissionClient;\n tokenManager: TokenManager;\n permissionEnabled: boolean;\n }) {\n this.permissionClient = options.permissionClient;\n this.tokenManager = options.tokenManager;\n this.permissionEnabled = options.permissionEnabled;\n }\n\n async authorize(\n requests: AuthorizeRequest[],\n options?: AuthorizeRequestOptions,\n ): Promise<AuthorizeResponse[]> {\n // Check if permissions are enabled before validating the server token. That\n // way when permissions are disabled, the noop token manager can be used\n // without fouling up the logic inside the ServerPermissionClient, because\n // the code path won't be reached.\n if (\n !this.permissionEnabled ||\n (await this.isValidServerToken(options?.token))\n ) {\n return requests.map(_ => ({ result: AuthorizeResult.ALLOW }));\n }\n return this.permissionClient.authorize(requests, options);\n }\n\n private async isValidServerToken(\n token: string | undefined,\n ): Promise<boolean> {\n if (!token) {\n return false;\n }\n return this.tokenManager\n .authenticate(token)\n .then(() => true)\n .catch(() => false);\n }\n}\n"],"names":["AuthorizeResult","z","Router","express","PermissionClient"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { PermissionCriteria, PermissionCondition, AuthorizeResult, AuthorizeRequest } from '@backstage/plugin-permission-common';
1
+ import { PermissionCriteria, AuthorizeRequest, AuthorizeResult, PermissionCondition, PermissionAuthorizer, AuthorizeRequestOptions, AuthorizeResponse } from '@backstage/plugin-permission-common';
2
+ import { BackstageIdentityResponse } from '@backstage/plugin-auth-backend';
2
3
  import { Router } from 'express';
3
- import { BackstageIdentity } from '@backstage/plugin-auth-backend';
4
+ import { PluginEndpointDiscovery, TokenManager } from '@backstage/backend-common';
5
+ import { Config } from '@backstage/config';
4
6
 
5
7
  /**
6
8
  * A conditional rule that can be provided in an
@@ -56,6 +58,65 @@ declare const createConditionFactory: <TParams extends any[]>(rule: PermissionRu
56
58
  params: TParams;
57
59
  };
58
60
 
61
+ /**
62
+ * An authorization request to be evaluated by the {@link PermissionPolicy}.
63
+ *
64
+ * @remarks
65
+ *
66
+ * This differs from {@link @backstage/permission-common#AuthorizeRequest} in that `resourceRef`
67
+ * should never be provided. This forces policies to be written in a way that's compatible with
68
+ * filtering collections of resources at data load time.
69
+ *
70
+ * @public
71
+ */
72
+ declare type PolicyAuthorizeRequest = Omit<AuthorizeRequest, 'resourceRef'>;
73
+ /**
74
+ * A conditional result to an authorization request, returned by the {@link PermissionPolicy}.
75
+ *
76
+ * @remarks
77
+ *
78
+ * This indicates that the policy allows authorization for the request, given that the returned
79
+ * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin
80
+ * which knows about the referenced permission rules.
81
+ *
82
+ * Similar to {@link @backstage/permission-common#AuthorizeResult}, but with the plugin and resource
83
+ * identifiers needed to evaluate the returned conditions.
84
+ * @public
85
+ */
86
+ declare type ConditionalPolicyDecision = {
87
+ result: AuthorizeResult.CONDITIONAL;
88
+ pluginId: string;
89
+ resourceType: string;
90
+ conditions: PermissionCriteria<PermissionCondition>;
91
+ };
92
+ /**
93
+ * The result of evaluating an authorization request with a {@link PermissionPolicy}.
94
+ *
95
+ * @public
96
+ */
97
+ declare type PolicyDecision = {
98
+ result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
99
+ } | ConditionalPolicyDecision;
100
+ /**
101
+ * A policy to evaluate authorization requests for any permissioned action performed in Backstage.
102
+ *
103
+ * @remarks
104
+ *
105
+ * This takes as input a permission and an optional Backstage identity, and should return ALLOW if
106
+ * the user is permitted to execute that action; otherwise DENY. For permissions relating to
107
+ * resources, such a catalog entities, a conditional response can also be returned. This states
108
+ * that the action is allowed if the conditions provided hold true.
109
+ *
110
+ * Conditions are a rule, and parameters to evaluate against that rule. For example, the rule might
111
+ * be `isOwner` and the parameters a collection of entityRefs; if one of the entityRefs matches
112
+ * the `owner` field on a catalog entity, this would resolve to ALLOW.
113
+ *
114
+ * @public
115
+ */
116
+ interface PermissionPolicy {
117
+ handle(request: PolicyAuthorizeRequest, user?: BackstageIdentityResponse): Promise<PolicyDecision>;
118
+ }
119
+
59
120
  /**
60
121
  * A utility type for mapping a single {@link PermissionRule} to its
61
122
  * corresponding {@link @backstage/plugin-permission-common#PermissionCondition}.
@@ -95,11 +156,7 @@ declare const createConditionExports: <TResource, TRules extends Record<string,
95
156
  rules: TRules;
96
157
  }) => {
97
158
  conditions: Conditions<TRules>;
98
- createConditions: (conditions: PermissionCriteria<PermissionCondition>) => {
99
- pluginId: string;
100
- resourceType: string;
101
- conditions: PermissionCriteria<PermissionCondition>;
102
- };
159
+ createPolicyDecision: (conditions: PermissionCriteria<PermissionCondition>) => ConditionalPolicyDecision;
103
160
  };
104
161
 
105
162
  /**
@@ -166,73 +223,32 @@ declare type ApplyConditionsResponse = {
166
223
  * This is used to construct the `createPermissionIntegrationRouter`, a function to add an
167
224
  * authorization route to your backend plugin. This route will be called by the `permission-backend`
168
225
  * when authorization conditions relating to this plugin need to be evaluated.
226
+ *
169
227
  * @public
170
228
  */
171
- declare const createPermissionIntegrationRouter: <TResource>({ resourceType, rules, getResource, }: {
229
+ declare const createPermissionIntegrationRouter: <TResource>(options: {
172
230
  resourceType: string;
173
231
  rules: PermissionRule<TResource, any, unknown[]>[];
174
232
  getResource: (resourceRef: string) => Promise<TResource | undefined>;
175
233
  }) => Router;
176
234
 
177
235
  /**
178
- * An authorization request to be evaluated by the {@link PermissionPolicy}.
179
- *
180
- * @remarks
181
- *
182
- * This differs from {@link @backstage/permission-common#AuthorizeRequest} in that `resourceRef`
183
- * should never be provided. This forces policies to be written in a way that's compatible with
184
- * filtering collections of resources at data load time.
185
- *
186
- * @public
187
- */
188
- declare type PolicyAuthorizeRequest = Omit<AuthorizeRequest, 'resourceRef'>;
189
- /**
190
- * A conditional result to an authorization request, returned by the {@link PermissionPolicy}.
191
- *
192
- * @remarks
193
- *
194
- * This indicates that the policy allows authorization for the request, given that the returned
195
- * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin
196
- * which knows about the referenced permission rules.
197
- *
198
- * Similar to {@link @backstage/permission-common#AuthorizeResult}, but with the plugin and resource
199
- * identifiers needed to evaluate the returned conditions.
236
+ * A thin wrapper around
237
+ * {@link @backstage/plugin-permission-common#PermissionClient} that allows all
238
+ * backend-to-backend requests.
200
239
  * @public
201
240
  */
202
- declare type ConditionalPolicyResult = {
203
- result: AuthorizeResult.CONDITIONAL;
204
- conditions: {
205
- pluginId: string;
206
- resourceType: string;
207
- conditions: PermissionCriteria<PermissionCondition>;
208
- };
209
- };
210
- /**
211
- * The result of evaluating an authorization request with a {@link PermissionPolicy}.
212
- *
213
- * @public
214
- */
215
- declare type PolicyResult = {
216
- result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
217
- } | ConditionalPolicyResult;
218
- /**
219
- * A policy to evaluate authorization requests for any permissioned action performed in Backstage.
220
- *
221
- * @remarks
222
- *
223
- * This takes as input a permission and an optional Backstage identity, and should return ALLOW if
224
- * the user is permitted to execute that action; otherwise DENY. For permissions relating to
225
- * resources, such a catalog entities, a conditional response can also be returned. This states
226
- * that the action is allowed if the conditions provided hold true.
227
- *
228
- * Conditions are a rule, and parameters to evaluate against that rule. For example, the rule might
229
- * be `isOwner` and the parameters a collection of entityRefs; if one of the entityRefs matches
230
- * the `owner` field on a catalog entity, this would resolve to ALLOW.
231
- *
232
- * @public
233
- */
234
- interface PermissionPolicy {
235
- handle(request: PolicyAuthorizeRequest, user?: BackstageIdentity): Promise<PolicyResult>;
241
+ declare class ServerPermissionClient implements PermissionAuthorizer {
242
+ private readonly permissionClient;
243
+ private readonly tokenManager;
244
+ private readonly permissionEnabled;
245
+ static fromConfig(config: Config, options: {
246
+ discovery: PluginEndpointDiscovery;
247
+ tokenManager: TokenManager;
248
+ }): ServerPermissionClient;
249
+ private constructor();
250
+ authorize(requests: AuthorizeRequest[], options?: AuthorizeRequestOptions): Promise<AuthorizeResponse[]>;
251
+ private isValidServerToken;
236
252
  }
237
253
 
238
- export { ApplyConditionsRequest, ApplyConditionsResponse, Condition, ConditionTransformer, ConditionalPolicyResult, Conditions, PermissionPolicy, PermissionRule, PolicyAuthorizeRequest, PolicyResult, createConditionExports, createConditionFactory, createConditionTransformer, createPermissionIntegrationRouter };
254
+ export { ApplyConditionsRequest, ApplyConditionsResponse, Condition, ConditionTransformer, ConditionalPolicyDecision, Conditions, PermissionPolicy, PermissionRule, PolicyAuthorizeRequest, PolicyDecision, ServerPermissionClient, createConditionExports, createConditionFactory, createConditionTransformer, createPermissionIntegrationRouter };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-permission-node",
3
3
  "description": "Common permission and authorization utilities for backend plugins",
4
- "version": "0.1.0",
4
+ "version": "0.2.3",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -29,19 +29,22 @@
29
29
  "clean": "backstage-cli clean"
30
30
  },
31
31
  "dependencies": {
32
- "@backstage/plugin-auth-backend": "^0.4.9",
33
- "@backstage/plugin-permission-common": "^0.2.0",
32
+ "@backstage/backend-common": "^0.10.1",
33
+ "@backstage/config": "^0.1.11",
34
+ "@backstage/plugin-auth-backend": "^0.6.0",
35
+ "@backstage/plugin-permission-common": "^0.3.0",
34
36
  "@types/express": "^4.17.6",
35
37
  "express": "^4.17.1",
36
38
  "zod": "^3.11.6"
37
39
  },
38
40
  "devDependencies": {
39
- "@backstage/cli": "^0.9.1",
41
+ "@backstage/cli": "^0.10.4",
40
42
  "@types/supertest": "^2.0.8",
43
+ "msw": "^0.35.0",
41
44
  "supertest": "^6.1.3"
42
45
  },
43
46
  "files": [
44
47
  "dist"
45
48
  ],
46
- "gitHead": "85c127e436a24140bb3606f17f034f82aa9c7c0d"
49
+ "gitHead": "4b2a8ed96ff427735c872a72c1864321ef698436"
47
50
  }