@backstage/plugin-permission-common 0.8.4-next.0 → 0.9.0-next.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,28 @@
1
1
  # @backstage/plugin-permission-common
2
2
 
3
+ ## 0.9.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests.
8
+
9
+ ### Patch Changes
10
+
11
+ - 72d019d: Removed various typos
12
+ - Updated dependencies
13
+ - @backstage/config@1.3.2
14
+ - @backstage/errors@1.2.7
15
+ - @backstage/types@1.2.1
16
+
17
+ ## 0.8.4
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - @backstage/types@1.2.1
23
+ - @backstage/config@1.3.2
24
+ - @backstage/errors@1.2.7
25
+
3
26
  ## 0.8.4-next.0
4
27
 
5
28
  ### Patch Changes
package/config.d.ts CHANGED
@@ -23,5 +23,10 @@ export interface Config {
23
23
  * @visibility frontend
24
24
  */
25
25
  enabled?: boolean;
26
+
27
+ /**
28
+ * @visibility frontend
29
+ */
30
+ EXPERIMENTAL_enableBatchedRequests?: boolean;
26
31
  };
27
32
  }
@@ -5,6 +5,7 @@ var fetch = require('cross-fetch');
5
5
  var uuid = require('uuid');
6
6
  var zod = require('zod');
7
7
  var api = require('./types/api.cjs.js');
8
+ var util = require('./permissions/util.cjs.js');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
11
 
@@ -39,6 +40,14 @@ const permissionCriteriaSchema = zod.z.lazy(
39
40
  const authorizePermissionResponseSchema = zod.z.object({
40
41
  result: zod.z.literal(api.AuthorizeResult.ALLOW).or(zod.z.literal(api.AuthorizeResult.DENY))
41
42
  });
43
+ const authorizePermissionResponseBatchSchema = zod.z.object({
44
+ result: zod.z.array(
45
+ zod.z.union([
46
+ zod.z.literal(api.AuthorizeResult.ALLOW),
47
+ zod.z.literal(api.AuthorizeResult.DENY)
48
+ ])
49
+ )
50
+ });
42
51
  const queryPermissionResponseSchema = zod.z.union([
43
52
  zod.z.object({
44
53
  result: zod.z.literal(api.AuthorizeResult.ALLOW).or(zod.z.literal(api.AuthorizeResult.DENY))
@@ -68,14 +77,24 @@ const responseSchema = (itemSchema, ids) => zod.z.object({
68
77
  class PermissionClient {
69
78
  enabled;
70
79
  discovery;
80
+ enableBatchedRequests;
71
81
  constructor(options) {
72
82
  this.discovery = options.discovery;
73
83
  this.enabled = options.config.getOptionalBoolean("permission.enabled") ?? false;
84
+ this.enableBatchedRequests = options.config.getOptionalBoolean(
85
+ "permission.EXPERIMENTAL_enableBatchedRequests"
86
+ ) ?? false;
74
87
  }
75
88
  /**
76
89
  * {@inheritdoc PermissionEvaluator.authorize}
77
90
  */
78
91
  async authorize(requests, options) {
92
+ if (!this.enabled) {
93
+ return requests.map((_) => ({ result: api.AuthorizeResult.ALLOW }));
94
+ }
95
+ if (this.enableBatchedRequests) {
96
+ return this.makeBatchedRequest(requests, options);
97
+ }
79
98
  return this.makeRequest(
80
99
  requests,
81
100
  authorizePermissionResponseSchema,
@@ -86,18 +105,67 @@ class PermissionClient {
86
105
  * {@inheritdoc PermissionEvaluator.authorizeConditional}
87
106
  */
88
107
  async authorizeConditional(queries, options) {
89
- return this.makeRequest(queries, queryPermissionResponseSchema, options);
90
- }
91
- async makeRequest(queries, itemSchema, options) {
92
108
  if (!this.enabled) {
93
109
  return queries.map((_) => ({ result: api.AuthorizeResult.ALLOW }));
94
110
  }
111
+ return this.makeRequest(queries, queryPermissionResponseSchema, options);
112
+ }
113
+ async makeRequest(queries, itemSchema, options) {
95
114
  const request = {
96
115
  items: queries.map((query) => ({
97
116
  id: uuid__namespace.v4(),
98
117
  ...query
99
118
  }))
100
119
  };
120
+ const parsedResponse = await this.makeRawRequest(
121
+ request,
122
+ itemSchema,
123
+ options
124
+ );
125
+ const responsesById = parsedResponse.items.reduce((acc, r) => {
126
+ acc[r.id] = r;
127
+ return acc;
128
+ }, {});
129
+ return request.items.map((query) => responsesById[query.id]);
130
+ }
131
+ async makeBatchedRequest(queries, options) {
132
+ const request = {};
133
+ for (const query of queries) {
134
+ const { permission, resourceRef } = query;
135
+ if (util.isResourcePermission(permission)) {
136
+ request[permission.name] ||= {
137
+ permission,
138
+ resourceRef: [],
139
+ id: uuid__namespace.v4()
140
+ };
141
+ if (resourceRef) {
142
+ request[permission.name].resourceRef?.push(resourceRef);
143
+ }
144
+ } else {
145
+ request[permission.name] ||= {
146
+ permission,
147
+ id: uuid__namespace.v4()
148
+ };
149
+ }
150
+ }
151
+ const parsedResponse = await this.makeRawRequest(
152
+ { items: Object.values(request) },
153
+ authorizePermissionResponseBatchSchema,
154
+ options
155
+ );
156
+ const responsesById = parsedResponse.items.reduce((acc, r) => {
157
+ acc[r.id] = r;
158
+ return acc;
159
+ }, {});
160
+ return queries.map((query) => {
161
+ const { id } = request[query.permission.name];
162
+ const item = responsesById[id];
163
+ return {
164
+ result: query.resourceRef ? item.result.shift() : item.result[0]
165
+ };
166
+ });
167
+ }
168
+ async makeRawRequest(request, itemSchema, options) {
101
169
  const permissionApi = await this.discovery.getBaseUrl("permission");
102
170
  const response = await fetch__default.default(`${permissionApi}/authorize`, {
103
171
  method: "POST",
@@ -111,15 +179,10 @@ class PermissionClient {
111
179
  throw await errors.ResponseError.fromResponse(response);
112
180
  }
113
181
  const responseBody = await response.json();
114
- const parsedResponse = responseSchema(
182
+ return responseSchema(
115
183
  itemSchema,
116
184
  new Set(request.items.map(({ id }) => id))
117
185
  ).parse(responseBody);
118
- const responsesById = parsedResponse.items.reduce((acc, r) => {
119
- acc[r.id] = r;
120
- return acc;
121
- }, {});
122
- return request.items.map((query) => responsesById[query.id]);
123
186
  }
124
187
  getAuthorizationHeader(token) {
125
188
  return token ? { Authorization: `Bearer ${token}` } : {};
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionClient.cjs.js","sources":["../src/PermissionClient.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 { Config } from '@backstage/config';\nimport { ResponseError } from '@backstage/errors';\nimport fetch from 'cross-fetch';\nimport * as uuid from 'uuid';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionMessageBatch,\n PermissionCriteria,\n PermissionCondition,\n PermissionEvaluator,\n QueryPermissionRequest,\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n QueryPermissionResponse,\n} from './types/api';\nimport { DiscoveryApi } from './types/discovery';\nimport { AuthorizeRequestOptions } from './types/permission';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z\n .object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n })\n .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ not: permissionCriteriaSchema })),\n);\n\nconst authorizePermissionResponseSchema: z.ZodSchema<AuthorizePermissionResponse> =\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n });\n\nconst queryPermissionResponseSchema: z.ZodSchema<QueryPermissionResponse> =\n z.union([\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n z.object({\n result: z.literal(AuthorizeResult.CONDITIONAL),\n pluginId: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ]);\n\nconst responseSchema = <T>(\n itemSchema: z.ZodSchema<T>,\n ids: Set<string>,\n): z.ZodSchema<PermissionMessageBatch<T>> =>\n z.object({\n items: z\n .array(\n z.intersection(\n z.object({\n id: z.string(),\n }),\n itemSchema,\n ),\n )\n .refine(\n items =>\n items.length === ids.size && items.every(({ id }) => ids.has(id)),\n {\n message: 'Items in response do not match request',\n },\n ),\n });\n\n/**\n * Options for {@link PermissionClient} requests.\n *\n * @public\n */\nexport type PermissionClientRequestOptions = {\n token?: string;\n};\n\n/**\n * An isomorphic client for requesting authorization for Backstage permissions.\n * @public\n */\nexport class PermissionClient implements PermissionEvaluator {\n private readonly enabled: boolean;\n private readonly discovery: DiscoveryApi;\n\n constructor(options: { discovery: DiscoveryApi; config: Config }) {\n this.discovery = options.discovery;\n this.enabled =\n options.config.getOptionalBoolean('permission.enabled') ?? false;\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorize}\n */\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n return this.makeRequest(\n requests,\n authorizePermissionResponseSchema,\n options,\n );\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorizeConditional}\n */\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<QueryPermissionResponse[]> {\n return this.makeRequest(queries, queryPermissionResponseSchema, options);\n }\n\n private async makeRequest<TQuery, TResult>(\n queries: TQuery[],\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n if (!this.enabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n const request: PermissionMessageBatch<TQuery> = {\n items: queries.map(query => ({\n id: uuid.v4(),\n ...query,\n })),\n };\n\n const permissionApi = await this.discovery.getBaseUrl('permission');\n const response = await fetch(`${permissionApi}/authorize`, {\n method: 'POST',\n body: JSON.stringify(request),\n headers: {\n ...this.getAuthorizationHeader(options?.token),\n 'content-type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const responseBody = await response.json();\n\n const parsedResponse = responseSchema(\n itemSchema,\n new Set(request.items.map(({ id }) => id)),\n ).parse(responseBody);\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, z.infer<typeof itemSchema>>);\n\n return request.items.map(query => responsesById[query.id]);\n }\n\n private getAuthorizationHeader(token?: string): Record<string, string> {\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n}\n"],"names":["z","AuthorizeResult","uuid","fetch","ResponseError"],"mappings":"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"}
1
+ {"version":3,"file":"PermissionClient.cjs.js","sources":["../src/PermissionClient.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 { Config } from '@backstage/config';\nimport { ResponseError } from '@backstage/errors';\nimport fetch from 'cross-fetch';\nimport * as uuid from 'uuid';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionMessageBatch,\n PermissionCriteria,\n PermissionCondition,\n PermissionEvaluator,\n QueryPermissionRequest,\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n QueryPermissionResponse,\n IdentifiedPermissionMessage,\n} from './types/api';\nimport { DiscoveryApi } from './types/discovery';\nimport {\n AuthorizeRequestOptions,\n BasicPermission,\n ResourcePermission,\n} from './types/permission';\nimport { isResourcePermission } from './permissions';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z\n .object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n })\n .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ not: permissionCriteriaSchema })),\n);\n\nconst authorizePermissionResponseSchema: z.ZodSchema<AuthorizePermissionResponse> =\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n });\n\nconst authorizePermissionResponseBatchSchema = z.object({\n result: z.array(\n z.union([\n z.literal(AuthorizeResult.ALLOW),\n z.literal(AuthorizeResult.DENY),\n ]),\n ),\n});\n\nconst queryPermissionResponseSchema: z.ZodSchema<QueryPermissionResponse> =\n z.union([\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n z.object({\n result: z.literal(AuthorizeResult.CONDITIONAL),\n pluginId: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ]);\n\nconst responseSchema = <T>(\n itemSchema: z.ZodSchema<T>,\n ids: Set<string>,\n): z.ZodSchema<PermissionMessageBatch<T>> =>\n z.object({\n items: z\n .array(\n z.intersection(\n z.object({\n id: z.string(),\n }),\n itemSchema,\n ),\n )\n .refine(\n items =>\n items.length === ids.size && items.every(({ id }) => ids.has(id)),\n {\n message: 'Items in response do not match request',\n },\n ),\n });\n\n/**\n * Options for {@link PermissionClient} requests.\n *\n * @public\n */\nexport type PermissionClientRequestOptions = {\n token?: string;\n};\n\n/**\n * An isomorphic client for requesting authorization for Backstage permissions.\n * @public\n */\nexport class PermissionClient implements PermissionEvaluator {\n private readonly enabled: boolean;\n private readonly discovery: DiscoveryApi;\n private readonly enableBatchedRequests: boolean;\n\n constructor(options: { discovery: DiscoveryApi; config: Config }) {\n this.discovery = options.discovery;\n this.enabled =\n options.config.getOptionalBoolean('permission.enabled') ?? false;\n\n this.enableBatchedRequests =\n options.config.getOptionalBoolean(\n 'permission.EXPERIMENTAL_enableBatchedRequests',\n ) ?? false;\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorize}\n */\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n if (!this.enabled) {\n return requests.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n if (this.enableBatchedRequests) {\n return this.makeBatchedRequest(requests, options);\n }\n\n return this.makeRequest(\n requests,\n authorizePermissionResponseSchema,\n options,\n );\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorizeConditional}\n */\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<QueryPermissionResponse[]> {\n if (!this.enabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n return this.makeRequest(queries, queryPermissionResponseSchema, options);\n }\n\n private async makeRequest<TQuery, TResult>(\n queries: TQuery[],\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n const request: PermissionMessageBatch<TQuery> = {\n items: queries.map(query => ({\n id: uuid.v4(),\n ...query,\n })),\n };\n\n const parsedResponse = await this.makeRawRequest(\n request,\n itemSchema,\n options,\n );\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, z.infer<typeof itemSchema>>);\n\n return request.items.map(query => responsesById[query.id]);\n }\n\n private async makeBatchedRequest(\n queries: AuthorizePermissionRequest[],\n options?: AuthorizeRequestOptions,\n ) {\n const request: Record<string, BatchedAuthorizePermissionRequest> = {};\n\n for (const query of queries) {\n const { permission, resourceRef } = query;\n\n if (isResourcePermission(permission)) {\n request[permission.name] ||= {\n permission,\n resourceRef: [],\n id: uuid.v4(),\n };\n\n if (resourceRef) {\n request[permission.name].resourceRef?.push(resourceRef);\n }\n } else {\n request[permission.name] ||= {\n permission,\n id: uuid.v4(),\n };\n }\n }\n\n const parsedResponse = await this.makeRawRequest(\n { items: Object.values(request) },\n authorizePermissionResponseBatchSchema,\n options,\n );\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, (typeof parsedResponse)['items'][number]>);\n\n return queries.map(query => {\n const { id } = request[query.permission.name];\n\n const item = responsesById[id];\n return {\n result: query.resourceRef ? item.result.shift()! : item.result[0],\n };\n });\n }\n\n private async makeRawRequest<TQuery, TResult>(\n request: PermissionMessageBatch<TQuery>,\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n const permissionApi = await this.discovery.getBaseUrl('permission');\n const response = await fetch(`${permissionApi}/authorize`, {\n method: 'POST',\n body: JSON.stringify(request),\n headers: {\n ...this.getAuthorizationHeader(options?.token),\n 'content-type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const responseBody = await response.json();\n\n return responseSchema(\n itemSchema,\n new Set(request.items.map(({ id }) => id)),\n ).parse(responseBody);\n }\n\n private getAuthorizationHeader(token?: string): Record<string, string> {\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n}\n\n/**\n * @internal\n */\nexport type BatchedAuthorizePermissionRequest = IdentifiedPermissionMessage<\n | {\n permission: BasicPermission;\n resourceRef?: undefined;\n }\n | { permission: ResourcePermission; resourceRef: string[] }\n>;\n"],"names":["z","AuthorizeResult","uuid","isResourcePermission","fetch","ResponseError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAM,2BAEFA,KAAE,CAAA,IAAA;AAAA,EAAK,MACTA,MACG,MAAO,CAAA;AAAA,IACN,IAAA,EAAMA,MAAE,MAAO,EAAA;AAAA,IACf,YAAA,EAAcA,MAAE,MAAO,EAAA;AAAA,IACvB,QAAQA,KAAE,CAAA,MAAA,CAAOA,MAAE,GAAI,EAAC,EAAE,QAAS;AAAA,GACpC,CACA,CAAA,EAAA,CAAGA,KAAE,CAAA,MAAA,CAAO,EAAE,KAAO,EAAAA,KAAA,CAAE,KAAM,CAAA,wBAAwB,EAAE,QAAS,EAAA,EAAG,CAAC,EACpE,EAAG,CAAAA,KAAA,CAAE,MAAO,CAAA,EAAE,OAAOA,KAAE,CAAA,KAAA,CAAM,wBAAwB,CAAA,CAAE,UAAW,EAAC,CAAC,CAAA,CACpE,GAAGA,KAAE,CAAA,MAAA,CAAO,EAAE,GAAK,EAAA,wBAAA,EAA0B,CAAC;AACnD,CAAA;AAEA,MAAM,iCAAA,GACJA,MAAE,MAAO,CAAA;AAAA,EACP,MAAA,EAAQA,KACL,CAAA,OAAA,CAAQC,mBAAgB,CAAA,KAAK,CAC7B,CAAA,EAAA,CAAGD,KAAE,CAAA,OAAA,CAAQC,mBAAgB,CAAA,IAAI,CAAC;AACvC,CAAC,CAAA;AAEH,MAAM,sCAAA,GAAyCD,MAAE,MAAO,CAAA;AAAA,EACtD,QAAQA,KAAE,CAAA,KAAA;AAAA,IACRA,MAAE,KAAM,CAAA;AAAA,MACNA,KAAA,CAAE,OAAQ,CAAAC,mBAAA,CAAgB,KAAK,CAAA;AAAA,MAC/BD,KAAA,CAAE,OAAQ,CAAAC,mBAAA,CAAgB,IAAI;AAAA,KAC/B;AAAA;AAEL,CAAC,CAAA;AAED,MAAM,6BAAA,GACJD,MAAE,KAAM,CAAA;AAAA,EACNA,MAAE,MAAO,CAAA;AAAA,IACP,MAAA,EAAQA,KACL,CAAA,OAAA,CAAQC,mBAAgB,CAAA,KAAK,CAC7B,CAAA,EAAA,CAAGD,KAAE,CAAA,OAAA,CAAQC,mBAAgB,CAAA,IAAI,CAAC;AAAA,GACtC,CAAA;AAAA,EACDD,MAAE,MAAO,CAAA;AAAA,IACP,MAAQ,EAAAA,KAAA,CAAE,OAAQ,CAAAC,mBAAA,CAAgB,WAAW,CAAA;AAAA,IAC7C,QAAA,EAAUD,MAAE,MAAO,EAAA;AAAA,IACnB,YAAA,EAAcA,MAAE,MAAO,EAAA;AAAA,IACvB,UAAY,EAAA;AAAA,GACb;AACH,CAAC,CAAA;AAEH,MAAM,cAAiB,GAAA,CACrB,UACA,EAAA,GAAA,KAEAA,MAAE,MAAO,CAAA;AAAA,EACP,OAAOA,KACJ,CAAA,KAAA;AAAA,IACCA,KAAE,CAAA,YAAA;AAAA,MACAA,MAAE,MAAO,CAAA;AAAA,QACP,EAAA,EAAIA,MAAE,MAAO;AAAA,OACd,CAAA;AAAA,MACD;AAAA;AACF,GAED,CAAA,MAAA;AAAA,IACC,CACE,KAAA,KAAA,KAAA,CAAM,MAAW,KAAA,GAAA,CAAI,QAAQ,KAAM,CAAA,KAAA,CAAM,CAAC,EAAE,EAAG,EAAA,KAAM,GAAI,CAAA,GAAA,CAAI,EAAE,CAAC,CAAA;AAAA,IAClE;AAAA,MACE,OAAS,EAAA;AAAA;AACX;AAEN,CAAC,CAAA;AAeI,MAAM,gBAAgD,CAAA;AAAA,EAC1C,OAAA;AAAA,EACA,SAAA;AAAA,EACA,qBAAA;AAAA,EAEjB,YAAY,OAAsD,EAAA;AAChE,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,OACH,GAAA,OAAA,CAAQ,MAAO,CAAA,kBAAA,CAAmB,oBAAoB,CAAK,IAAA,KAAA;AAE7D,IAAK,IAAA,CAAA,qBAAA,GACH,QAAQ,MAAO,CAAA,kBAAA;AAAA,MACb;AAAA,KACG,IAAA,KAAA;AAAA;AACT;AAAA;AAAA;AAAA,EAKA,MAAM,SACJ,CAAA,QAAA,EACA,OACwC,EAAA;AACxC,IAAI,IAAA,CAAC,KAAK,OAAS,EAAA;AACjB,MAAA,OAAO,SAAS,GAAI,CAAA,CAAA,CAAA,MAAM,EAAE,MAAQ,EAAAC,mBAAA,CAAgB,OAAiB,CAAA,CAAA;AAAA;AAGvE,IAAA,IAAI,KAAK,qBAAuB,EAAA;AAC9B,MAAO,OAAA,IAAA,CAAK,kBAAmB,CAAA,QAAA,EAAU,OAAO,CAAA;AAAA;AAGlD,IAAA,OAAO,IAAK,CAAA,WAAA;AAAA,MACV,QAAA;AAAA,MACA,iCAAA;AAAA,MACA;AAAA,KACF;AAAA;AACF;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,CAAA,OAAA,EACA,OACoC,EAAA;AACpC,IAAI,IAAA,CAAC,KAAK,OAAS,EAAA;AACjB,MAAA,OAAO,QAAQ,GAAI,CAAA,CAAA,CAAA,MAAM,EAAE,MAAQ,EAAAA,mBAAA,CAAgB,OAAiB,CAAA,CAAA;AAAA;AAGtE,IAAA,OAAO,IAAK,CAAA,WAAA,CAAY,OAAS,EAAA,6BAAA,EAA+B,OAAO,CAAA;AAAA;AACzE,EAEA,MAAc,WAAA,CACZ,OACA,EAAA,UAAA,EACA,OACA,EAAA;AACA,IAAA,MAAM,OAA0C,GAAA;AAAA,MAC9C,KAAA,EAAO,OAAQ,CAAA,GAAA,CAAI,CAAU,KAAA,MAAA;AAAA,QAC3B,EAAA,EAAIC,gBAAK,EAAG,EAAA;AAAA,QACZ,GAAG;AAAA,OACH,CAAA;AAAA,KACJ;AAEA,IAAM,MAAA,cAAA,GAAiB,MAAM,IAAK,CAAA,cAAA;AAAA,MAChC,OAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,MAAM,gBAAgB,cAAe,CAAA,KAAA,CAAM,MAAO,CAAA,CAAC,KAAK,CAAM,KAAA;AAC5D,MAAI,GAAA,CAAA,CAAA,CAAE,EAAE,CAAI,GAAA,CAAA;AACZ,MAAO,OAAA,GAAA;AAAA,KACT,EAAG,EAAgD,CAAA;AAEnD,IAAA,OAAO,QAAQ,KAAM,CAAA,GAAA,CAAI,WAAS,aAAc,CAAA,KAAA,CAAM,EAAE,CAAC,CAAA;AAAA;AAC3D,EAEA,MAAc,kBACZ,CAAA,OAAA,EACA,OACA,EAAA;AACA,IAAA,MAAM,UAA6D,EAAC;AAEpE,IAAA,KAAA,MAAW,SAAS,OAAS,EAAA;AAC3B,MAAM,MAAA,EAAE,UAAY,EAAA,WAAA,EAAgB,GAAA,KAAA;AAEpC,MAAI,IAAAC,yBAAA,CAAqB,UAAU,CAAG,EAAA;AACpC,QAAQ,OAAA,CAAA,UAAA,CAAW,IAAI,CAAM,KAAA;AAAA,UAC3B,UAAA;AAAA,UACA,aAAa,EAAC;AAAA,UACd,EAAA,EAAID,gBAAK,EAAG;AAAA,SACd;AAEA,QAAA,IAAI,WAAa,EAAA;AACf,UAAA,OAAA,CAAQ,UAAW,CAAA,IAAI,CAAE,CAAA,WAAA,EAAa,KAAK,WAAW,CAAA;AAAA;AACxD,OACK,MAAA;AACL,QAAQ,OAAA,CAAA,UAAA,CAAW,IAAI,CAAM,KAAA;AAAA,UAC3B,UAAA;AAAA,UACA,EAAA,EAAIA,gBAAK,EAAG;AAAA,SACd;AAAA;AACF;AAGF,IAAM,MAAA,cAAA,GAAiB,MAAM,IAAK,CAAA,cAAA;AAAA,MAChC,EAAE,KAAA,EAAO,MAAO,CAAA,MAAA,CAAO,OAAO,CAAE,EAAA;AAAA,MAChC,sCAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,MAAM,gBAAgB,cAAe,CAAA,KAAA,CAAM,MAAO,CAAA,CAAC,KAAK,CAAM,KAAA;AAC5D,MAAI,GAAA,CAAA,CAAA,CAAE,EAAE,CAAI,GAAA,CAAA;AACZ,MAAO,OAAA,GAAA;AAAA,KACT,EAAG,EAA8D,CAAA;AAEjE,IAAO,OAAA,OAAA,CAAQ,IAAI,CAAS,KAAA,KAAA;AAC1B,MAAA,MAAM,EAAE,EAAG,EAAA,GAAI,OAAQ,CAAA,KAAA,CAAM,WAAW,IAAI,CAAA;AAE5C,MAAM,MAAA,IAAA,GAAO,cAAc,EAAE,CAAA;AAC7B,MAAO,OAAA;AAAA,QACL,MAAA,EAAQ,MAAM,WAAc,GAAA,IAAA,CAAK,OAAO,KAAM,EAAA,GAAK,IAAK,CAAA,MAAA,CAAO,CAAC;AAAA,OAClE;AAAA,KACD,CAAA;AAAA;AACH,EAEA,MAAc,cAAA,CACZ,OACA,EAAA,UAAA,EACA,OACA,EAAA;AACA,IAAA,MAAM,aAAgB,GAAA,MAAM,IAAK,CAAA,SAAA,CAAU,WAAW,YAAY,CAAA;AAClE,IAAA,MAAM,QAAW,GAAA,MAAME,sBAAM,CAAA,CAAA,EAAG,aAAa,CAAc,UAAA,CAAA,EAAA;AAAA,MACzD,MAAQ,EAAA,MAAA;AAAA,MACR,IAAA,EAAM,IAAK,CAAA,SAAA,CAAU,OAAO,CAAA;AAAA,MAC5B,OAAS,EAAA;AAAA,QACP,GAAG,IAAA,CAAK,sBAAuB,CAAA,OAAA,EAAS,KAAK,CAAA;AAAA,QAC7C,cAAgB,EAAA;AAAA;AAClB,KACD,CAAA;AACD,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAM,MAAA,YAAA,GAAe,MAAM,QAAA,CAAS,IAAK,EAAA;AAEzC,IAAO,OAAA,cAAA;AAAA,MACL,UAAA;AAAA,MACA,IAAI,GAAI,CAAA,OAAA,CAAQ,KAAM,CAAA,GAAA,CAAI,CAAC,EAAE,EAAA,EAAS,KAAA,EAAE,CAAC;AAAA,KAC3C,CAAE,MAAM,YAAY,CAAA;AAAA;AACtB,EAEQ,uBAAuB,KAAwC,EAAA;AACrE,IAAA,OAAO,QAAQ,EAAE,aAAA,EAAe,UAAU,KAAK,CAAA,CAAA,KAAO,EAAC;AAAA;AAE3D;;;;"}
@@ -3,6 +3,7 @@ import fetch from 'cross-fetch';
3
3
  import * as uuid from 'uuid';
4
4
  import { z } from 'zod';
5
5
  import { AuthorizeResult } from './types/api.esm.js';
6
+ import { isResourcePermission } from './permissions/util.esm.js';
6
7
 
7
8
  const permissionCriteriaSchema = z.lazy(
8
9
  () => z.object({
@@ -14,6 +15,14 @@ const permissionCriteriaSchema = z.lazy(
14
15
  const authorizePermissionResponseSchema = z.object({
15
16
  result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
16
17
  });
18
+ const authorizePermissionResponseBatchSchema = z.object({
19
+ result: z.array(
20
+ z.union([
21
+ z.literal(AuthorizeResult.ALLOW),
22
+ z.literal(AuthorizeResult.DENY)
23
+ ])
24
+ )
25
+ });
17
26
  const queryPermissionResponseSchema = z.union([
18
27
  z.object({
19
28
  result: z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY))
@@ -43,14 +52,24 @@ const responseSchema = (itemSchema, ids) => z.object({
43
52
  class PermissionClient {
44
53
  enabled;
45
54
  discovery;
55
+ enableBatchedRequests;
46
56
  constructor(options) {
47
57
  this.discovery = options.discovery;
48
58
  this.enabled = options.config.getOptionalBoolean("permission.enabled") ?? false;
59
+ this.enableBatchedRequests = options.config.getOptionalBoolean(
60
+ "permission.EXPERIMENTAL_enableBatchedRequests"
61
+ ) ?? false;
49
62
  }
50
63
  /**
51
64
  * {@inheritdoc PermissionEvaluator.authorize}
52
65
  */
53
66
  async authorize(requests, options) {
67
+ if (!this.enabled) {
68
+ return requests.map((_) => ({ result: AuthorizeResult.ALLOW }));
69
+ }
70
+ if (this.enableBatchedRequests) {
71
+ return this.makeBatchedRequest(requests, options);
72
+ }
54
73
  return this.makeRequest(
55
74
  requests,
56
75
  authorizePermissionResponseSchema,
@@ -61,18 +80,67 @@ class PermissionClient {
61
80
  * {@inheritdoc PermissionEvaluator.authorizeConditional}
62
81
  */
63
82
  async authorizeConditional(queries, options) {
64
- return this.makeRequest(queries, queryPermissionResponseSchema, options);
65
- }
66
- async makeRequest(queries, itemSchema, options) {
67
83
  if (!this.enabled) {
68
84
  return queries.map((_) => ({ result: AuthorizeResult.ALLOW }));
69
85
  }
86
+ return this.makeRequest(queries, queryPermissionResponseSchema, options);
87
+ }
88
+ async makeRequest(queries, itemSchema, options) {
70
89
  const request = {
71
90
  items: queries.map((query) => ({
72
91
  id: uuid.v4(),
73
92
  ...query
74
93
  }))
75
94
  };
95
+ const parsedResponse = await this.makeRawRequest(
96
+ request,
97
+ itemSchema,
98
+ options
99
+ );
100
+ const responsesById = parsedResponse.items.reduce((acc, r) => {
101
+ acc[r.id] = r;
102
+ return acc;
103
+ }, {});
104
+ return request.items.map((query) => responsesById[query.id]);
105
+ }
106
+ async makeBatchedRequest(queries, options) {
107
+ const request = {};
108
+ for (const query of queries) {
109
+ const { permission, resourceRef } = query;
110
+ if (isResourcePermission(permission)) {
111
+ request[permission.name] ||= {
112
+ permission,
113
+ resourceRef: [],
114
+ id: uuid.v4()
115
+ };
116
+ if (resourceRef) {
117
+ request[permission.name].resourceRef?.push(resourceRef);
118
+ }
119
+ } else {
120
+ request[permission.name] ||= {
121
+ permission,
122
+ id: uuid.v4()
123
+ };
124
+ }
125
+ }
126
+ const parsedResponse = await this.makeRawRequest(
127
+ { items: Object.values(request) },
128
+ authorizePermissionResponseBatchSchema,
129
+ options
130
+ );
131
+ const responsesById = parsedResponse.items.reduce((acc, r) => {
132
+ acc[r.id] = r;
133
+ return acc;
134
+ }, {});
135
+ return queries.map((query) => {
136
+ const { id } = request[query.permission.name];
137
+ const item = responsesById[id];
138
+ return {
139
+ result: query.resourceRef ? item.result.shift() : item.result[0]
140
+ };
141
+ });
142
+ }
143
+ async makeRawRequest(request, itemSchema, options) {
76
144
  const permissionApi = await this.discovery.getBaseUrl("permission");
77
145
  const response = await fetch(`${permissionApi}/authorize`, {
78
146
  method: "POST",
@@ -86,15 +154,10 @@ class PermissionClient {
86
154
  throw await ResponseError.fromResponse(response);
87
155
  }
88
156
  const responseBody = await response.json();
89
- const parsedResponse = responseSchema(
157
+ return responseSchema(
90
158
  itemSchema,
91
159
  new Set(request.items.map(({ id }) => id))
92
160
  ).parse(responseBody);
93
- const responsesById = parsedResponse.items.reduce((acc, r) => {
94
- acc[r.id] = r;
95
- return acc;
96
- }, {});
97
- return request.items.map((query) => responsesById[query.id]);
98
161
  }
99
162
  getAuthorizationHeader(token) {
100
163
  return token ? { Authorization: `Bearer ${token}` } : {};
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionClient.esm.js","sources":["../src/PermissionClient.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 { Config } from '@backstage/config';\nimport { ResponseError } from '@backstage/errors';\nimport fetch from 'cross-fetch';\nimport * as uuid from 'uuid';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionMessageBatch,\n PermissionCriteria,\n PermissionCondition,\n PermissionEvaluator,\n QueryPermissionRequest,\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n QueryPermissionResponse,\n} from './types/api';\nimport { DiscoveryApi } from './types/discovery';\nimport { AuthorizeRequestOptions } from './types/permission';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z\n .object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n })\n .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ not: permissionCriteriaSchema })),\n);\n\nconst authorizePermissionResponseSchema: z.ZodSchema<AuthorizePermissionResponse> =\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n });\n\nconst queryPermissionResponseSchema: z.ZodSchema<QueryPermissionResponse> =\n z.union([\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n z.object({\n result: z.literal(AuthorizeResult.CONDITIONAL),\n pluginId: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ]);\n\nconst responseSchema = <T>(\n itemSchema: z.ZodSchema<T>,\n ids: Set<string>,\n): z.ZodSchema<PermissionMessageBatch<T>> =>\n z.object({\n items: z\n .array(\n z.intersection(\n z.object({\n id: z.string(),\n }),\n itemSchema,\n ),\n )\n .refine(\n items =>\n items.length === ids.size && items.every(({ id }) => ids.has(id)),\n {\n message: 'Items in response do not match request',\n },\n ),\n });\n\n/**\n * Options for {@link PermissionClient} requests.\n *\n * @public\n */\nexport type PermissionClientRequestOptions = {\n token?: string;\n};\n\n/**\n * An isomorphic client for requesting authorization for Backstage permissions.\n * @public\n */\nexport class PermissionClient implements PermissionEvaluator {\n private readonly enabled: boolean;\n private readonly discovery: DiscoveryApi;\n\n constructor(options: { discovery: DiscoveryApi; config: Config }) {\n this.discovery = options.discovery;\n this.enabled =\n options.config.getOptionalBoolean('permission.enabled') ?? false;\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorize}\n */\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n return this.makeRequest(\n requests,\n authorizePermissionResponseSchema,\n options,\n );\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorizeConditional}\n */\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<QueryPermissionResponse[]> {\n return this.makeRequest(queries, queryPermissionResponseSchema, options);\n }\n\n private async makeRequest<TQuery, TResult>(\n queries: TQuery[],\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n if (!this.enabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n const request: PermissionMessageBatch<TQuery> = {\n items: queries.map(query => ({\n id: uuid.v4(),\n ...query,\n })),\n };\n\n const permissionApi = await this.discovery.getBaseUrl('permission');\n const response = await fetch(`${permissionApi}/authorize`, {\n method: 'POST',\n body: JSON.stringify(request),\n headers: {\n ...this.getAuthorizationHeader(options?.token),\n 'content-type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const responseBody = await response.json();\n\n const parsedResponse = responseSchema(\n itemSchema,\n new Set(request.items.map(({ id }) => id)),\n ).parse(responseBody);\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, z.infer<typeof itemSchema>>);\n\n return request.items.map(query => responsesById[query.id]);\n }\n\n private getAuthorizationHeader(token?: string): Record<string, string> {\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PermissionClient.esm.js","sources":["../src/PermissionClient.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 { Config } from '@backstage/config';\nimport { ResponseError } from '@backstage/errors';\nimport fetch from 'cross-fetch';\nimport * as uuid from 'uuid';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n PermissionMessageBatch,\n PermissionCriteria,\n PermissionCondition,\n PermissionEvaluator,\n QueryPermissionRequest,\n AuthorizePermissionRequest,\n AuthorizePermissionResponse,\n QueryPermissionResponse,\n IdentifiedPermissionMessage,\n} from './types/api';\nimport { DiscoveryApi } from './types/discovery';\nimport {\n AuthorizeRequestOptions,\n BasicPermission,\n ResourcePermission,\n} from './types/permission';\nimport { isResourcePermission } from './permissions';\n\nconst permissionCriteriaSchema: z.ZodSchema<\n PermissionCriteria<PermissionCondition>\n> = z.lazy(() =>\n z\n .object({\n rule: z.string(),\n resourceType: z.string(),\n params: z.record(z.any()).optional(),\n })\n .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() }))\n .or(z.object({ not: permissionCriteriaSchema })),\n);\n\nconst authorizePermissionResponseSchema: z.ZodSchema<AuthorizePermissionResponse> =\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n });\n\nconst authorizePermissionResponseBatchSchema = z.object({\n result: z.array(\n z.union([\n z.literal(AuthorizeResult.ALLOW),\n z.literal(AuthorizeResult.DENY),\n ]),\n ),\n});\n\nconst queryPermissionResponseSchema: z.ZodSchema<QueryPermissionResponse> =\n z.union([\n z.object({\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n z.object({\n result: z.literal(AuthorizeResult.CONDITIONAL),\n pluginId: z.string(),\n resourceType: z.string(),\n conditions: permissionCriteriaSchema,\n }),\n ]);\n\nconst responseSchema = <T>(\n itemSchema: z.ZodSchema<T>,\n ids: Set<string>,\n): z.ZodSchema<PermissionMessageBatch<T>> =>\n z.object({\n items: z\n .array(\n z.intersection(\n z.object({\n id: z.string(),\n }),\n itemSchema,\n ),\n )\n .refine(\n items =>\n items.length === ids.size && items.every(({ id }) => ids.has(id)),\n {\n message: 'Items in response do not match request',\n },\n ),\n });\n\n/**\n * Options for {@link PermissionClient} requests.\n *\n * @public\n */\nexport type PermissionClientRequestOptions = {\n token?: string;\n};\n\n/**\n * An isomorphic client for requesting authorization for Backstage permissions.\n * @public\n */\nexport class PermissionClient implements PermissionEvaluator {\n private readonly enabled: boolean;\n private readonly discovery: DiscoveryApi;\n private readonly enableBatchedRequests: boolean;\n\n constructor(options: { discovery: DiscoveryApi; config: Config }) {\n this.discovery = options.discovery;\n this.enabled =\n options.config.getOptionalBoolean('permission.enabled') ?? false;\n\n this.enableBatchedRequests =\n options.config.getOptionalBoolean(\n 'permission.EXPERIMENTAL_enableBatchedRequests',\n ) ?? false;\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorize}\n */\n async authorize(\n requests: AuthorizePermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<AuthorizePermissionResponse[]> {\n if (!this.enabled) {\n return requests.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n if (this.enableBatchedRequests) {\n return this.makeBatchedRequest(requests, options);\n }\n\n return this.makeRequest(\n requests,\n authorizePermissionResponseSchema,\n options,\n );\n }\n\n /**\n * {@inheritdoc PermissionEvaluator.authorizeConditional}\n */\n async authorizeConditional(\n queries: QueryPermissionRequest[],\n options?: PermissionClientRequestOptions,\n ): Promise<QueryPermissionResponse[]> {\n if (!this.enabled) {\n return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));\n }\n\n return this.makeRequest(queries, queryPermissionResponseSchema, options);\n }\n\n private async makeRequest<TQuery, TResult>(\n queries: TQuery[],\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n const request: PermissionMessageBatch<TQuery> = {\n items: queries.map(query => ({\n id: uuid.v4(),\n ...query,\n })),\n };\n\n const parsedResponse = await this.makeRawRequest(\n request,\n itemSchema,\n options,\n );\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, z.infer<typeof itemSchema>>);\n\n return request.items.map(query => responsesById[query.id]);\n }\n\n private async makeBatchedRequest(\n queries: AuthorizePermissionRequest[],\n options?: AuthorizeRequestOptions,\n ) {\n const request: Record<string, BatchedAuthorizePermissionRequest> = {};\n\n for (const query of queries) {\n const { permission, resourceRef } = query;\n\n if (isResourcePermission(permission)) {\n request[permission.name] ||= {\n permission,\n resourceRef: [],\n id: uuid.v4(),\n };\n\n if (resourceRef) {\n request[permission.name].resourceRef?.push(resourceRef);\n }\n } else {\n request[permission.name] ||= {\n permission,\n id: uuid.v4(),\n };\n }\n }\n\n const parsedResponse = await this.makeRawRequest(\n { items: Object.values(request) },\n authorizePermissionResponseBatchSchema,\n options,\n );\n\n const responsesById = parsedResponse.items.reduce((acc, r) => {\n acc[r.id] = r;\n return acc;\n }, {} as Record<string, (typeof parsedResponse)['items'][number]>);\n\n return queries.map(query => {\n const { id } = request[query.permission.name];\n\n const item = responsesById[id];\n return {\n result: query.resourceRef ? item.result.shift()! : item.result[0],\n };\n });\n }\n\n private async makeRawRequest<TQuery, TResult>(\n request: PermissionMessageBatch<TQuery>,\n itemSchema: z.ZodSchema<TResult>,\n options?: AuthorizeRequestOptions,\n ) {\n const permissionApi = await this.discovery.getBaseUrl('permission');\n const response = await fetch(`${permissionApi}/authorize`, {\n method: 'POST',\n body: JSON.stringify(request),\n headers: {\n ...this.getAuthorizationHeader(options?.token),\n 'content-type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const responseBody = await response.json();\n\n return responseSchema(\n itemSchema,\n new Set(request.items.map(({ id }) => id)),\n ).parse(responseBody);\n }\n\n private getAuthorizationHeader(token?: string): Record<string, string> {\n return token ? { Authorization: `Bearer ${token}` } : {};\n }\n}\n\n/**\n * @internal\n */\nexport type BatchedAuthorizePermissionRequest = IdentifiedPermissionMessage<\n | {\n permission: BasicPermission;\n resourceRef?: undefined;\n }\n | { permission: ResourcePermission; resourceRef: string[] }\n>;\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -216,6 +216,7 @@ type EvaluatePermissionRequest = {
216
216
  /**
217
217
  * A batch of requests sent to the permission backend.
218
218
  * @public
219
+ * @deprecated This type is not used and it will be removed in the future
219
220
  */
220
221
  type EvaluatePermissionRequestBatch = PermissionMessageBatch<EvaluatePermissionRequest>;
221
222
  /**
@@ -406,6 +407,7 @@ type PermissionClientRequestOptions = {
406
407
  declare class PermissionClient implements PermissionEvaluator {
407
408
  private readonly enabled;
408
409
  private readonly discovery;
410
+ private readonly enableBatchedRequests;
409
411
  constructor(options: {
410
412
  discovery: DiscoveryApi;
411
413
  config: Config;
@@ -419,6 +421,8 @@ declare class PermissionClient implements PermissionEvaluator {
419
421
  */
420
422
  authorizeConditional(queries: QueryPermissionRequest[], options?: PermissionClientRequestOptions): Promise<QueryPermissionResponse[]>;
421
423
  private makeRequest;
424
+ private makeBatchedRequest;
425
+ private makeRawRequest;
422
426
  private getAuthorizationHeader;
423
427
  }
424
428
 
@@ -1 +1 @@
1
- {"version":3,"file":"api.cjs.js","sources":["../../src/types/api.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 { JsonPrimitive } from '@backstage/types';\nimport { Permission, ResourcePermission } from './permission';\n\n/**\n * A request with a UUID identifier, so that batched responses can be matched up with the original\n * requests.\n * @public\n */\nexport type IdentifiedPermissionMessage<T> = T & { id: string };\n\n/**\n * A batch of request or response items.\n * @public\n */\nexport type PermissionMessageBatch<T> = {\n items: IdentifiedPermissionMessage<T>[];\n};\n\n/**\n * The result of an authorization request.\n * @public\n */\nexport enum AuthorizeResult {\n /**\n * The authorization request is denied.\n */\n DENY = 'DENY',\n /**\n * The authorization request is allowed.\n */\n ALLOW = 'ALLOW',\n /**\n * The authorization request is allowed if the provided conditions are met.\n */\n CONDITIONAL = 'CONDITIONAL',\n}\n\n/**\n * A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy unconditionally allows (or denies) the request.\n *\n * @public\n */\nexport type DefinitivePolicyDecision = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\n/**\n * A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy allows authorization for the request, given that the returned\n * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin\n * which knows about the referenced permission rules.\n *\n * @public\n */\nexport type ConditionalPolicyDecision = {\n result: AuthorizeResult.CONDITIONAL;\n pluginId: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n};\n\n/**\n * A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @public\n */\nexport type PolicyDecision =\n | DefinitivePolicyDecision\n | ConditionalPolicyDecision;\n\n/**\n * A condition returned with a CONDITIONAL authorization response.\n *\n * Conditions are a reference to a rule defined by a plugin, and parameters to apply the rule. For\n * example, a rule might be `isOwner` from the catalog-backend, and params may be a list of entity\n * claims from a identity token.\n * @public\n */\nexport type PermissionCondition<\n TResourceType extends string = string,\n TParams extends PermissionRuleParams = PermissionRuleParams,\n> = {\n resourceType: TResourceType;\n rule: string;\n params?: TParams;\n};\n\n/**\n * Utility type to represent an array with 1 or more elements.\n * @ignore\n */\ntype NonEmptyArray<T> = [T, ...T[]];\n\n/**\n * Represents a logical AND for the provided criteria.\n * @public\n */\nexport type AllOfCriteria<TQuery> = {\n allOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a logical OR for the provided criteria.\n * @public\n */\nexport type AnyOfCriteria<TQuery> = {\n anyOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a negation of the provided criteria.\n * @public\n */\nexport type NotCriteria<TQuery> = {\n not: PermissionCriteria<TQuery>;\n};\n\n/**\n * Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.\n * @public\n */\nexport type PermissionCriteria<TQuery> =\n | AllOfCriteria<TQuery>\n | AnyOfCriteria<TQuery>\n | NotCriteria<TQuery>\n | TQuery;\n\n/**\n * A parameter to a permission rule.\n *\n * @public\n */\nexport type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[];\n\n/**\n * Types that can be used as parameters to permission rules.\n *\n * @public\n */\nexport type PermissionRuleParams =\n | undefined\n | Record<string, PermissionRuleParam>;\n\n/**\n * An individual request sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequest = {\n permission: Permission;\n resourceRef?: string;\n};\n\n/**\n * A batch of requests sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequestBatch =\n PermissionMessageBatch<EvaluatePermissionRequest>;\n\n/**\n * An individual response from the permission backend.\n *\n * @remarks\n *\n * This response type is an alias of {@link PolicyDecision} to maintain separation between the\n * {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend\n * api. They may diverge at some point in the future. The response\n *\n * @public\n */\nexport type EvaluatePermissionResponse = PolicyDecision;\n\n/**\n * A batch of responses from the permission backend.\n * @public\n */\nexport type EvaluatePermissionResponseBatch =\n PermissionMessageBatch<EvaluatePermissionResponse>;\n\n/**\n * Request object for {@link PermissionEvaluator.authorize}. If a {@link ResourcePermission}\n * is provided, it must include a corresponding `resourceRef`.\n * @public\n */\nexport type AuthorizePermissionRequest =\n | {\n permission: Exclude<Permission, ResourcePermission>;\n resourceRef?: never;\n }\n | { permission: ResourcePermission; resourceRef: string };\n\n/**\n * Response object for {@link PermissionEvaluator.authorize}.\n * @public\n */\nexport type AuthorizePermissionResponse = DefinitivePolicyDecision;\n\n/**\n * Request object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionRequest = {\n permission: ResourcePermission;\n resourceRef?: never;\n};\n\n/**\n * Response object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionResponse = PolicyDecision;\n\n/**\n * A client interacting with the permission backend can implement this evaluator interface.\n *\n * @public\n */\nexport interface PermissionEvaluator {\n /**\n * Evaluates {@link Permission | Permissions} and returns a definitive decision.\n */\n authorize(\n requests: AuthorizePermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<AuthorizePermissionResponse[]>;\n\n /**\n * Evaluates {@link ResourcePermission | ResourcePermissions} and returns both definitive and\n * conditional decisions, depending on the configured\n * {@link @backstage/plugin-permission-node#PermissionPolicy}. This method is useful when the\n * caller needs more control over the processing of conditional decisions. For example, a plugin\n * backend may want to use {@link PermissionCriteria | conditions} in a database query instead of\n * evaluating each resource in memory.\n */\n authorizeConditional(\n requests: QueryPermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<QueryPermissionResponse[]>;\n}\n\n// Note(Rugvip): I kept the below type around in case we want to add new options\n// in the future, for example a signal. It also helps out enabling API\n// constraints, as without this we can't have the permissions service implement\n// the evaluator interface due to the mismatch in parameter count.\n\n/**\n * Options for {@link PermissionEvaluator} requests.\n *\n * This is currently empty, as there are no longer any common options for the permission evaluator.\n *\n * @public\n */\nexport interface EvaluatorRequestOptions {}\n"],"names":["AuthorizeResult"],"mappings":";;AAsCY,IAAA,eAAA,qBAAAA,gBAAL,KAAA;AAIL,EAAAA,iBAAA,MAAO,CAAA,GAAA,MAAA;AAIP,EAAAA,iBAAA,OAAQ,CAAA,GAAA,OAAA;AAIR,EAAAA,iBAAA,aAAc,CAAA,GAAA,aAAA;AAZJ,EAAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA;;;;"}
1
+ {"version":3,"file":"api.cjs.js","sources":["../../src/types/api.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 { JsonPrimitive } from '@backstage/types';\nimport { Permission, ResourcePermission } from './permission';\n\n/**\n * A request with a UUID identifier, so that batched responses can be matched up with the original\n * requests.\n * @public\n */\nexport type IdentifiedPermissionMessage<T> = T & { id: string };\n\n/**\n * A batch of request or response items.\n * @public\n */\nexport type PermissionMessageBatch<T> = {\n items: IdentifiedPermissionMessage<T>[];\n};\n\n/**\n * The result of an authorization request.\n * @public\n */\nexport enum AuthorizeResult {\n /**\n * The authorization request is denied.\n */\n DENY = 'DENY',\n /**\n * The authorization request is allowed.\n */\n ALLOW = 'ALLOW',\n /**\n * The authorization request is allowed if the provided conditions are met.\n */\n CONDITIONAL = 'CONDITIONAL',\n}\n\n/**\n * A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy unconditionally allows (or denies) the request.\n *\n * @public\n */\nexport type DefinitivePolicyDecision = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\n/**\n * A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy allows authorization for the request, given that the returned\n * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin\n * which knows about the referenced permission rules.\n *\n * @public\n */\nexport type ConditionalPolicyDecision = {\n result: AuthorizeResult.CONDITIONAL;\n pluginId: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n};\n\n/**\n * A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @public\n */\nexport type PolicyDecision =\n | DefinitivePolicyDecision\n | ConditionalPolicyDecision;\n\n/**\n * A condition returned with a CONDITIONAL authorization response.\n *\n * Conditions are a reference to a rule defined by a plugin, and parameters to apply the rule. For\n * example, a rule might be `isOwner` from the catalog-backend, and params may be a list of entity\n * claims from a identity token.\n * @public\n */\nexport type PermissionCondition<\n TResourceType extends string = string,\n TParams extends PermissionRuleParams = PermissionRuleParams,\n> = {\n resourceType: TResourceType;\n rule: string;\n params?: TParams;\n};\n\n/**\n * Utility type to represent an array with 1 or more elements.\n * @ignore\n */\ntype NonEmptyArray<T> = [T, ...T[]];\n\n/**\n * Represents a logical AND for the provided criteria.\n * @public\n */\nexport type AllOfCriteria<TQuery> = {\n allOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a logical OR for the provided criteria.\n * @public\n */\nexport type AnyOfCriteria<TQuery> = {\n anyOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a negation of the provided criteria.\n * @public\n */\nexport type NotCriteria<TQuery> = {\n not: PermissionCriteria<TQuery>;\n};\n\n/**\n * Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.\n * @public\n */\nexport type PermissionCriteria<TQuery> =\n | AllOfCriteria<TQuery>\n | AnyOfCriteria<TQuery>\n | NotCriteria<TQuery>\n | TQuery;\n\n/**\n * A parameter to a permission rule.\n *\n * @public\n */\nexport type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[];\n\n/**\n * Types that can be used as parameters to permission rules.\n *\n * @public\n */\nexport type PermissionRuleParams =\n | undefined\n | Record<string, PermissionRuleParam>;\n\n/**\n * An individual request sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequest = {\n permission: Permission;\n resourceRef?: string;\n};\n\n/**\n * A batch of requests sent to the permission backend.\n * @public\n * @deprecated This type is not used and it will be removed in the future\n */\nexport type EvaluatePermissionRequestBatch =\n PermissionMessageBatch<EvaluatePermissionRequest>;\n\n/**\n * An individual response from the permission backend.\n *\n * @remarks\n *\n * This response type is an alias of {@link PolicyDecision} to maintain separation between the\n * {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend\n * api. They may diverge at some point in the future. The response\n *\n * @public\n */\nexport type EvaluatePermissionResponse = PolicyDecision;\n\n/**\n * A batch of responses from the permission backend.\n * @public\n */\nexport type EvaluatePermissionResponseBatch =\n PermissionMessageBatch<EvaluatePermissionResponse>;\n\n/**\n * Request object for {@link PermissionEvaluator.authorize}. If a {@link ResourcePermission}\n * is provided, it must include a corresponding `resourceRef`.\n * @public\n */\nexport type AuthorizePermissionRequest =\n | {\n permission: Exclude<Permission, ResourcePermission>;\n resourceRef?: never;\n }\n | { permission: ResourcePermission; resourceRef: string };\n\n/**\n * Response object for {@link PermissionEvaluator.authorize}.\n * @public\n */\nexport type AuthorizePermissionResponse = DefinitivePolicyDecision;\n\n/**\n * Request object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionRequest = {\n permission: ResourcePermission;\n resourceRef?: never;\n};\n\n/**\n * Response object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionResponse = PolicyDecision;\n\n/**\n * A client interacting with the permission backend can implement this evaluator interface.\n *\n * @public\n */\nexport interface PermissionEvaluator {\n /**\n * Evaluates {@link Permission | Permissions} and returns a definitive decision.\n */\n authorize(\n requests: AuthorizePermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<AuthorizePermissionResponse[]>;\n\n /**\n * Evaluates {@link ResourcePermission | ResourcePermissions} and returns both definitive and\n * conditional decisions, depending on the configured\n * {@link @backstage/plugin-permission-node#PermissionPolicy}. This method is useful when the\n * caller needs more control over the processing of conditional decisions. For example, a plugin\n * backend may want to use {@link PermissionCriteria | conditions} in a database query instead of\n * evaluating each resource in memory.\n */\n authorizeConditional(\n requests: QueryPermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<QueryPermissionResponse[]>;\n}\n\n// Note(Rugvip): I kept the below type around in case we want to add new options\n// in the future, for example a signal. It also helps out enabling API\n// constraints, as without this we can't have the permissions service implement\n// the evaluator interface due to the mismatch in parameter count.\n\n/**\n * Options for {@link PermissionEvaluator} requests.\n *\n * This is currently empty, as there are no longer any common options for the permission evaluator.\n *\n * @public\n */\nexport interface EvaluatorRequestOptions {}\n"],"names":["AuthorizeResult"],"mappings":";;AAsCY,IAAA,eAAA,qBAAAA,gBAAL,KAAA;AAIL,EAAAA,iBAAA,MAAO,CAAA,GAAA,MAAA;AAIP,EAAAA,iBAAA,OAAQ,CAAA,GAAA,OAAA;AAIR,EAAAA,iBAAA,aAAc,CAAA,GAAA,aAAA;AAZJ,EAAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.esm.js","sources":["../../src/types/api.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 { JsonPrimitive } from '@backstage/types';\nimport { Permission, ResourcePermission } from './permission';\n\n/**\n * A request with a UUID identifier, so that batched responses can be matched up with the original\n * requests.\n * @public\n */\nexport type IdentifiedPermissionMessage<T> = T & { id: string };\n\n/**\n * A batch of request or response items.\n * @public\n */\nexport type PermissionMessageBatch<T> = {\n items: IdentifiedPermissionMessage<T>[];\n};\n\n/**\n * The result of an authorization request.\n * @public\n */\nexport enum AuthorizeResult {\n /**\n * The authorization request is denied.\n */\n DENY = 'DENY',\n /**\n * The authorization request is allowed.\n */\n ALLOW = 'ALLOW',\n /**\n * The authorization request is allowed if the provided conditions are met.\n */\n CONDITIONAL = 'CONDITIONAL',\n}\n\n/**\n * A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy unconditionally allows (or denies) the request.\n *\n * @public\n */\nexport type DefinitivePolicyDecision = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\n/**\n * A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy allows authorization for the request, given that the returned\n * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin\n * which knows about the referenced permission rules.\n *\n * @public\n */\nexport type ConditionalPolicyDecision = {\n result: AuthorizeResult.CONDITIONAL;\n pluginId: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n};\n\n/**\n * A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @public\n */\nexport type PolicyDecision =\n | DefinitivePolicyDecision\n | ConditionalPolicyDecision;\n\n/**\n * A condition returned with a CONDITIONAL authorization response.\n *\n * Conditions are a reference to a rule defined by a plugin, and parameters to apply the rule. For\n * example, a rule might be `isOwner` from the catalog-backend, and params may be a list of entity\n * claims from a identity token.\n * @public\n */\nexport type PermissionCondition<\n TResourceType extends string = string,\n TParams extends PermissionRuleParams = PermissionRuleParams,\n> = {\n resourceType: TResourceType;\n rule: string;\n params?: TParams;\n};\n\n/**\n * Utility type to represent an array with 1 or more elements.\n * @ignore\n */\ntype NonEmptyArray<T> = [T, ...T[]];\n\n/**\n * Represents a logical AND for the provided criteria.\n * @public\n */\nexport type AllOfCriteria<TQuery> = {\n allOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a logical OR for the provided criteria.\n * @public\n */\nexport type AnyOfCriteria<TQuery> = {\n anyOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a negation of the provided criteria.\n * @public\n */\nexport type NotCriteria<TQuery> = {\n not: PermissionCriteria<TQuery>;\n};\n\n/**\n * Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.\n * @public\n */\nexport type PermissionCriteria<TQuery> =\n | AllOfCriteria<TQuery>\n | AnyOfCriteria<TQuery>\n | NotCriteria<TQuery>\n | TQuery;\n\n/**\n * A parameter to a permission rule.\n *\n * @public\n */\nexport type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[];\n\n/**\n * Types that can be used as parameters to permission rules.\n *\n * @public\n */\nexport type PermissionRuleParams =\n | undefined\n | Record<string, PermissionRuleParam>;\n\n/**\n * An individual request sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequest = {\n permission: Permission;\n resourceRef?: string;\n};\n\n/**\n * A batch of requests sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequestBatch =\n PermissionMessageBatch<EvaluatePermissionRequest>;\n\n/**\n * An individual response from the permission backend.\n *\n * @remarks\n *\n * This response type is an alias of {@link PolicyDecision} to maintain separation between the\n * {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend\n * api. They may diverge at some point in the future. The response\n *\n * @public\n */\nexport type EvaluatePermissionResponse = PolicyDecision;\n\n/**\n * A batch of responses from the permission backend.\n * @public\n */\nexport type EvaluatePermissionResponseBatch =\n PermissionMessageBatch<EvaluatePermissionResponse>;\n\n/**\n * Request object for {@link PermissionEvaluator.authorize}. If a {@link ResourcePermission}\n * is provided, it must include a corresponding `resourceRef`.\n * @public\n */\nexport type AuthorizePermissionRequest =\n | {\n permission: Exclude<Permission, ResourcePermission>;\n resourceRef?: never;\n }\n | { permission: ResourcePermission; resourceRef: string };\n\n/**\n * Response object for {@link PermissionEvaluator.authorize}.\n * @public\n */\nexport type AuthorizePermissionResponse = DefinitivePolicyDecision;\n\n/**\n * Request object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionRequest = {\n permission: ResourcePermission;\n resourceRef?: never;\n};\n\n/**\n * Response object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionResponse = PolicyDecision;\n\n/**\n * A client interacting with the permission backend can implement this evaluator interface.\n *\n * @public\n */\nexport interface PermissionEvaluator {\n /**\n * Evaluates {@link Permission | Permissions} and returns a definitive decision.\n */\n authorize(\n requests: AuthorizePermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<AuthorizePermissionResponse[]>;\n\n /**\n * Evaluates {@link ResourcePermission | ResourcePermissions} and returns both definitive and\n * conditional decisions, depending on the configured\n * {@link @backstage/plugin-permission-node#PermissionPolicy}. This method is useful when the\n * caller needs more control over the processing of conditional decisions. For example, a plugin\n * backend may want to use {@link PermissionCriteria | conditions} in a database query instead of\n * evaluating each resource in memory.\n */\n authorizeConditional(\n requests: QueryPermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<QueryPermissionResponse[]>;\n}\n\n// Note(Rugvip): I kept the below type around in case we want to add new options\n// in the future, for example a signal. It also helps out enabling API\n// constraints, as without this we can't have the permissions service implement\n// the evaluator interface due to the mismatch in parameter count.\n\n/**\n * Options for {@link PermissionEvaluator} requests.\n *\n * This is currently empty, as there are no longer any common options for the permission evaluator.\n *\n * @public\n */\nexport interface EvaluatorRequestOptions {}\n"],"names":["AuthorizeResult"],"mappings":"AAsCY,IAAA,eAAA,qBAAAA,gBAAL,KAAA;AAIL,EAAAA,iBAAA,MAAO,CAAA,GAAA,MAAA;AAIP,EAAAA,iBAAA,OAAQ,CAAA,GAAA,OAAA;AAIR,EAAAA,iBAAA,aAAc,CAAA,GAAA,aAAA;AAZJ,EAAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA;;;;"}
1
+ {"version":3,"file":"api.esm.js","sources":["../../src/types/api.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 { JsonPrimitive } from '@backstage/types';\nimport { Permission, ResourcePermission } from './permission';\n\n/**\n * A request with a UUID identifier, so that batched responses can be matched up with the original\n * requests.\n * @public\n */\nexport type IdentifiedPermissionMessage<T> = T & { id: string };\n\n/**\n * A batch of request or response items.\n * @public\n */\nexport type PermissionMessageBatch<T> = {\n items: IdentifiedPermissionMessage<T>[];\n};\n\n/**\n * The result of an authorization request.\n * @public\n */\nexport enum AuthorizeResult {\n /**\n * The authorization request is denied.\n */\n DENY = 'DENY',\n /**\n * The authorization request is allowed.\n */\n ALLOW = 'ALLOW',\n /**\n * The authorization request is allowed if the provided conditions are met.\n */\n CONDITIONAL = 'CONDITIONAL',\n}\n\n/**\n * A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy unconditionally allows (or denies) the request.\n *\n * @public\n */\nexport type DefinitivePolicyDecision = {\n result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;\n};\n\n/**\n * A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @remarks\n *\n * This indicates that the policy allows authorization for the request, given that the returned\n * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin\n * which knows about the referenced permission rules.\n *\n * @public\n */\nexport type ConditionalPolicyDecision = {\n result: AuthorizeResult.CONDITIONAL;\n pluginId: string;\n resourceType: string;\n conditions: PermissionCriteria<PermissionCondition>;\n};\n\n/**\n * A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}.\n *\n * @public\n */\nexport type PolicyDecision =\n | DefinitivePolicyDecision\n | ConditionalPolicyDecision;\n\n/**\n * A condition returned with a CONDITIONAL authorization response.\n *\n * Conditions are a reference to a rule defined by a plugin, and parameters to apply the rule. For\n * example, a rule might be `isOwner` from the catalog-backend, and params may be a list of entity\n * claims from a identity token.\n * @public\n */\nexport type PermissionCondition<\n TResourceType extends string = string,\n TParams extends PermissionRuleParams = PermissionRuleParams,\n> = {\n resourceType: TResourceType;\n rule: string;\n params?: TParams;\n};\n\n/**\n * Utility type to represent an array with 1 or more elements.\n * @ignore\n */\ntype NonEmptyArray<T> = [T, ...T[]];\n\n/**\n * Represents a logical AND for the provided criteria.\n * @public\n */\nexport type AllOfCriteria<TQuery> = {\n allOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a logical OR for the provided criteria.\n * @public\n */\nexport type AnyOfCriteria<TQuery> = {\n anyOf: NonEmptyArray<PermissionCriteria<TQuery>>;\n};\n\n/**\n * Represents a negation of the provided criteria.\n * @public\n */\nexport type NotCriteria<TQuery> = {\n not: PermissionCriteria<TQuery>;\n};\n\n/**\n * Composes several {@link PermissionCondition}s as criteria with a nested AND/OR structure.\n * @public\n */\nexport type PermissionCriteria<TQuery> =\n | AllOfCriteria<TQuery>\n | AnyOfCriteria<TQuery>\n | NotCriteria<TQuery>\n | TQuery;\n\n/**\n * A parameter to a permission rule.\n *\n * @public\n */\nexport type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[];\n\n/**\n * Types that can be used as parameters to permission rules.\n *\n * @public\n */\nexport type PermissionRuleParams =\n | undefined\n | Record<string, PermissionRuleParam>;\n\n/**\n * An individual request sent to the permission backend.\n * @public\n */\nexport type EvaluatePermissionRequest = {\n permission: Permission;\n resourceRef?: string;\n};\n\n/**\n * A batch of requests sent to the permission backend.\n * @public\n * @deprecated This type is not used and it will be removed in the future\n */\nexport type EvaluatePermissionRequestBatch =\n PermissionMessageBatch<EvaluatePermissionRequest>;\n\n/**\n * An individual response from the permission backend.\n *\n * @remarks\n *\n * This response type is an alias of {@link PolicyDecision} to maintain separation between the\n * {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend\n * api. They may diverge at some point in the future. The response\n *\n * @public\n */\nexport type EvaluatePermissionResponse = PolicyDecision;\n\n/**\n * A batch of responses from the permission backend.\n * @public\n */\nexport type EvaluatePermissionResponseBatch =\n PermissionMessageBatch<EvaluatePermissionResponse>;\n\n/**\n * Request object for {@link PermissionEvaluator.authorize}. If a {@link ResourcePermission}\n * is provided, it must include a corresponding `resourceRef`.\n * @public\n */\nexport type AuthorizePermissionRequest =\n | {\n permission: Exclude<Permission, ResourcePermission>;\n resourceRef?: never;\n }\n | { permission: ResourcePermission; resourceRef: string };\n\n/**\n * Response object for {@link PermissionEvaluator.authorize}.\n * @public\n */\nexport type AuthorizePermissionResponse = DefinitivePolicyDecision;\n\n/**\n * Request object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionRequest = {\n permission: ResourcePermission;\n resourceRef?: never;\n};\n\n/**\n * Response object for {@link PermissionEvaluator.authorizeConditional}.\n * @public\n */\nexport type QueryPermissionResponse = PolicyDecision;\n\n/**\n * A client interacting with the permission backend can implement this evaluator interface.\n *\n * @public\n */\nexport interface PermissionEvaluator {\n /**\n * Evaluates {@link Permission | Permissions} and returns a definitive decision.\n */\n authorize(\n requests: AuthorizePermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<AuthorizePermissionResponse[]>;\n\n /**\n * Evaluates {@link ResourcePermission | ResourcePermissions} and returns both definitive and\n * conditional decisions, depending on the configured\n * {@link @backstage/plugin-permission-node#PermissionPolicy}. This method is useful when the\n * caller needs more control over the processing of conditional decisions. For example, a plugin\n * backend may want to use {@link PermissionCriteria | conditions} in a database query instead of\n * evaluating each resource in memory.\n */\n authorizeConditional(\n requests: QueryPermissionRequest[],\n options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options\n ): Promise<QueryPermissionResponse[]>;\n}\n\n// Note(Rugvip): I kept the below type around in case we want to add new options\n// in the future, for example a signal. It also helps out enabling API\n// constraints, as without this we can't have the permissions service implement\n// the evaluator interface due to the mismatch in parameter count.\n\n/**\n * Options for {@link PermissionEvaluator} requests.\n *\n * This is currently empty, as there are no longer any common options for the permission evaluator.\n *\n * @public\n */\nexport interface EvaluatorRequestOptions {}\n"],"names":["AuthorizeResult"],"mappings":"AAsCY,IAAA,eAAA,qBAAAA,gBAAL,KAAA;AAIL,EAAAA,iBAAA,MAAO,CAAA,GAAA,MAAA;AAIP,EAAAA,iBAAA,OAAQ,CAAA,GAAA,OAAA;AAIR,EAAAA,iBAAA,aAAc,CAAA,GAAA,aAAA;AAZJ,EAAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-permission-common",
3
- "version": "0.8.4-next.0",
3
+ "version": "0.9.0-next.0",
4
4
  "description": "Isomorphic types and client for Backstage permissions and authorization",
5
5
  "backstage": {
6
6
  "role": "common-library",
@@ -48,23 +48,23 @@
48
48
  "test": "backstage-cli package test"
49
49
  },
50
50
  "dependencies": {
51
- "@backstage/config": "1.3.2-next.0",
52
- "@backstage/errors": "1.2.7-next.0",
53
- "@backstage/types": "1.2.1-next.0",
51
+ "@backstage/config": "1.3.2",
52
+ "@backstage/errors": "1.2.7",
53
+ "@backstage/types": "1.2.1",
54
54
  "cross-fetch": "^4.0.0",
55
55
  "uuid": "^11.0.0",
56
56
  "zod": "^3.22.4",
57
57
  "zod-to-json-schema": "^3.20.4"
58
58
  },
59
59
  "devDependencies": {
60
- "@backstage/cli": "0.29.5-next.1",
60
+ "@backstage/cli": "0.32.1-next.1",
61
61
  "msw": "^1.0.0"
62
62
  },
63
63
  "configSchema": "config.d.ts",
64
64
  "typesVersions": {
65
65
  "*": {
66
- "index": [
67
- "dist/index.d.ts"
66
+ "package.json": [
67
+ "package.json"
68
68
  ]
69
69
  }
70
70
  },