@backstage/plugin-permission-backend 0.5.36 → 0.5.37-next.1

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,34 +1,34 @@
1
1
  # @backstage/plugin-permission-backend
2
2
 
3
- ## 0.5.36
3
+ ## 0.5.37-next.1
4
4
 
5
5
  ### Patch Changes
6
6
 
7
7
  - Updated dependencies
8
- - @backstage/backend-common@0.21.3
9
- - @backstage/plugin-auth-node@0.4.8
10
- - @backstage/plugin-permission-node@0.7.24
11
- - @backstage/backend-plugin-api@0.6.13
8
+ - @backstage/config@1.2.0-next.1
9
+ - @backstage/backend-common@0.21.4-next.1
10
+ - @backstage/backend-plugin-api@0.6.14-next.1
11
+ - @backstage/plugin-auth-node@0.4.9-next.1
12
+ - @backstage/plugin-permission-common@0.7.13-next.1
13
+ - @backstage/plugin-permission-node@0.7.25-next.1
14
+ - @backstage/errors@1.2.4-next.0
12
15
 
13
- ## 0.5.35
16
+ ## 0.5.36-next.0
14
17
 
15
18
  ### Patch Changes
16
19
 
17
- - Updated dependencies
18
- - @backstage/backend-common@0.21.2
19
- - @backstage/plugin-auth-node@0.4.7
20
- - @backstage/plugin-permission-node@0.7.23
21
- - @backstage/backend-plugin-api@0.6.12
22
-
23
- ## 0.5.34
20
+ - 9802004: Migrated to use the new auth services introduced in [BEP-0003](https://github.com/backstage/backstage/blob/master/beps/0003-auth-architecture-evolution/README.md).
24
21
 
25
- ### Patch Changes
22
+ The `createRouter` function now accepts `auth`, `httpAuth` and `userInfo` options. Theses are used internally to support the new backend system, and can be ignored.
26
23
 
27
24
  - Updated dependencies
28
- - @backstage/backend-common@0.21.1
29
- - @backstage/plugin-auth-node@0.4.6
30
- - @backstage/plugin-permission-node@0.7.22
31
- - @backstage/backend-plugin-api@0.6.11
25
+ - @backstage/backend-common@0.21.3-next.0
26
+ - @backstage/plugin-auth-node@0.4.8-next.0
27
+ - @backstage/errors@1.2.4-next.0
28
+ - @backstage/backend-plugin-api@0.6.13-next.0
29
+ - @backstage/plugin-permission-common@0.7.13-next.0
30
+ - @backstage/plugin-permission-node@0.7.24-next.0
31
+ - @backstage/config@1.1.2-next.0
32
32
 
33
33
  ## 0.5.33
34
34
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-permission-backend",
3
- "version": "0.5.36",
3
+ "version": "0.5.37-next.1",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var backendCommon = require('@backstage/backend-common');
6
6
  var backendPluginApi = require('@backstage/backend-plugin-api');
7
7
  var alpha = require('@backstage/plugin-permission-node/alpha');
8
- var router = require('./cjs/router-40c490a9.cjs.js');
8
+ var router = require('./cjs/router-ced2cd8a.cjs.js');
9
9
  require('zod');
10
10
  require('express');
11
11
  require('express-promise-router');
@@ -43,9 +43,19 @@ const permissionPlugin = backendPluginApi.createBackendPlugin({
43
43
  config: backendPluginApi.coreServices.rootConfig,
44
44
  logger: backendPluginApi.coreServices.logger,
45
45
  discovery: backendPluginApi.coreServices.discovery,
46
- identity: backendPluginApi.coreServices.identity
46
+ auth: backendPluginApi.coreServices.auth,
47
+ httpAuth: backendPluginApi.coreServices.httpAuth,
48
+ userInfo: backendPluginApi.coreServices.userInfo
47
49
  },
48
- async init({ http, config, logger, discovery, identity }) {
50
+ async init({
51
+ http,
52
+ config,
53
+ logger,
54
+ discovery,
55
+ auth,
56
+ httpAuth,
57
+ userInfo
58
+ }) {
49
59
  const winstonLogger = backendCommon.loggerToWinstonLogger(logger);
50
60
  if (!policies.policy) {
51
61
  throw new Error(
@@ -56,9 +66,11 @@ const permissionPlugin = backendPluginApi.createBackendPlugin({
56
66
  await router.createRouter({
57
67
  config,
58
68
  discovery,
59
- identity,
60
69
  logger: winstonLogger,
61
- policy: policies.policy
70
+ policy: policies.policy,
71
+ auth,
72
+ httpAuth,
73
+ userInfo
62
74
  })
63
75
  );
64
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":["../src/plugin.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 { loggerToWinstonLogger } from '@backstage/backend-common';\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { PermissionPolicy } from '@backstage/plugin-permission-node';\nimport {\n policyExtensionPoint,\n PolicyExtensionPoint,\n} from '@backstage/plugin-permission-node/alpha';\nimport { createRouter } from './service';\n\nclass PolicyExtensionPointImpl implements PolicyExtensionPoint {\n public policy: PermissionPolicy | undefined;\n\n setPolicy(policy: PermissionPolicy): void {\n if (this.policy) {\n throw new Error('Policy already set');\n }\n this.policy = policy;\n }\n}\n\n/**\n * Permission plugin\n *\n * @alpha\n */\nexport const permissionPlugin = createBackendPlugin({\n pluginId: 'permission',\n register(env) {\n const policies = new PolicyExtensionPointImpl();\n\n env.registerExtensionPoint(policyExtensionPoint, policies);\n\n env.registerInit({\n deps: {\n http: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n identity: coreServices.identity,\n },\n async init({ http, config, logger, discovery, identity }) {\n const winstonLogger = loggerToWinstonLogger(logger);\n if (!policies.policy) {\n throw new Error(\n 'No policy module installed! Please install a policy module. If you want to allow all requests, use @backstage/plugin-permission-backend-module-allow-all-policy permissionModuleAllowAllPolicy',\n );\n }\n\n http.use(\n await createRouter({\n config,\n discovery,\n identity,\n logger: winstonLogger,\n policy: policies.policy,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","policyExtensionPoint","coreServices","loggerToWinstonLogger","createRouter"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,wBAAyD,CAAA;AAAA,EAA/D,WAAA,GAAA;AACE,IAAO,aAAA,CAAA,IAAA,EAAA,QAAA,CAAA,CAAA;AAAA,GAAA;AAAA,EAEP,UAAU,MAAgC,EAAA;AACxC,IAAA,IAAI,KAAK,MAAQ,EAAA;AACf,MAAM,MAAA,IAAI,MAAM,oBAAoB,CAAA,CAAA;AAAA,KACtC;AACA,IAAA,IAAA,CAAK,MAAS,GAAA,MAAA,CAAA;AAAA,GAChB;AACF,CAAA;AAOO,MAAM,mBAAmBA,oCAAoB,CAAA;AAAA,EAClD,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,QAAA,GAAW,IAAI,wBAAyB,EAAA,CAAA;AAE9C,IAAI,GAAA,CAAA,sBAAA,CAAuBC,4BAAsB,QAAQ,CAAA,CAAA;AAEzD,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,UAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,UAAUA,6BAAa,CAAA,QAAA;AAAA,OACzB;AAAA,MACA,MAAM,KAAK,EAAE,IAAA,EAAM,QAAQ,MAAQ,EAAA,SAAA,EAAW,UAAY,EAAA;AACxD,QAAM,MAAA,aAAA,GAAgBC,oCAAsB,MAAM,CAAA,CAAA;AAClD,QAAI,IAAA,CAAC,SAAS,MAAQ,EAAA;AACpB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,gMAAA;AAAA,WACF,CAAA;AAAA,SACF;AAEA,QAAK,IAAA,CAAA,GAAA;AAAA,UACH,MAAMC,mBAAa,CAAA;AAAA,YACjB,MAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAA;AAAA,YACA,MAAQ,EAAA,aAAA;AAAA,YACR,QAAQ,QAAS,CAAA,MAAA;AAAA,WAClB,CAAA;AAAA,SACH,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":["../src/plugin.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 { loggerToWinstonLogger } from '@backstage/backend-common';\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { PermissionPolicy } from '@backstage/plugin-permission-node';\nimport {\n policyExtensionPoint,\n PolicyExtensionPoint,\n} from '@backstage/plugin-permission-node/alpha';\nimport { createRouter } from './service';\n\nclass PolicyExtensionPointImpl implements PolicyExtensionPoint {\n public policy: PermissionPolicy | undefined;\n\n setPolicy(policy: PermissionPolicy): void {\n if (this.policy) {\n throw new Error('Policy already set');\n }\n this.policy = policy;\n }\n}\n\n/**\n * Permission plugin\n *\n * @alpha\n */\nexport const permissionPlugin = createBackendPlugin({\n pluginId: 'permission',\n register(env) {\n const policies = new PolicyExtensionPointImpl();\n\n env.registerExtensionPoint(policyExtensionPoint, policies);\n\n env.registerInit({\n deps: {\n http: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n discovery: coreServices.discovery,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n },\n async init({\n http,\n config,\n logger,\n discovery,\n auth,\n httpAuth,\n userInfo,\n }) {\n const winstonLogger = loggerToWinstonLogger(logger);\n if (!policies.policy) {\n throw new Error(\n 'No policy module installed! Please install a policy module. If you want to allow all requests, use @backstage/plugin-permission-backend-module-allow-all-policy permissionModuleAllowAllPolicy',\n );\n }\n\n http.use(\n await createRouter({\n config,\n discovery,\n logger: winstonLogger,\n policy: policies.policy,\n auth,\n httpAuth,\n userInfo,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","policyExtensionPoint","coreServices","loggerToWinstonLogger","createRouter"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,wBAAyD,CAAA;AAAA,EAA/D,WAAA,GAAA;AACE,IAAO,aAAA,CAAA,IAAA,EAAA,QAAA,CAAA,CAAA;AAAA,GAAA;AAAA,EAEP,UAAU,MAAgC,EAAA;AACxC,IAAA,IAAI,KAAK,MAAQ,EAAA;AACf,MAAM,MAAA,IAAI,MAAM,oBAAoB,CAAA,CAAA;AAAA,KACtC;AACA,IAAA,IAAA,CAAK,MAAS,GAAA,MAAA,CAAA;AAAA,GAChB;AACF,CAAA;AAOO,MAAM,mBAAmBA,oCAAoB,CAAA;AAAA,EAClD,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAM,MAAA,QAAA,GAAW,IAAI,wBAAyB,EAAA,CAAA;AAE9C,IAAI,GAAA,CAAA,sBAAA,CAAuBC,4BAAsB,QAAQ,CAAA,CAAA;AAEzD,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,UAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,MAAMA,6BAAa,CAAA,IAAA;AAAA,QACnB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,UAAUA,6BAAa,CAAA,QAAA;AAAA,OACzB;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,IAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,OACC,EAAA;AACD,QAAM,MAAA,aAAA,GAAgBC,oCAAsB,MAAM,CAAA,CAAA;AAClD,QAAI,IAAA,CAAC,SAAS,MAAQ,EAAA;AACpB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,gMAAA;AAAA,WACF,CAAA;AAAA,SACF;AAEA,QAAK,IAAA,CAAA,GAAA;AAAA,UACH,MAAMC,mBAAa,CAAA;AAAA,YACjB,MAAA;AAAA,YACA,SAAA;AAAA,YACA,MAAQ,EAAA,aAAA;AAAA,YACR,QAAQ,QAAS,CAAA,MAAA;AAAA,YACjB,IAAA;AAAA,YACA,QAAA;AAAA,YACA,QAAA;AAAA,WACD,CAAA;AAAA,SACH,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
@@ -34,12 +34,17 @@ const responseSchema = zod.z.object({
34
34
  class PermissionIntegrationClient {
35
35
  constructor(options) {
36
36
  __publicField(this, "discovery");
37
+ __publicField(this, "auth");
37
38
  this.discovery = options.discovery;
39
+ this.auth = options.auth;
38
40
  }
39
- async applyConditions(pluginId, decisions, authHeader) {
40
- const endpoint = `${await this.discovery.getBaseUrl(
41
- pluginId
42
- )}/.well-known/backstage/permissions/apply-conditions`;
41
+ async applyConditions(pluginId, credentials, decisions) {
42
+ const baseUrl = await this.discovery.getBaseUrl(pluginId);
43
+ const endpoint = `${baseUrl}/.well-known/backstage/permissions/apply-conditions`;
44
+ const token = this.auth.isPrincipal(credentials, "none") ? void 0 : await this.auth.getPluginRequestToken({
45
+ onBehalfOf: credentials,
46
+ targetPluginId: pluginId
47
+ }).then((t) => t.token);
43
48
  const response = await fetch__default["default"](endpoint, {
44
49
  method: "POST",
45
50
  body: JSON.stringify({
@@ -53,7 +58,7 @@ class PermissionIntegrationClient {
53
58
  )
54
59
  }),
55
60
  headers: {
56
- ...authHeader ? { authorization: authHeader } : {},
61
+ ...token ? { authorization: `Bearer ${token}` } : {},
57
62
  "content-type": "application/json"
58
63
  }
59
64
  });
@@ -96,12 +101,29 @@ const evaluatePermissionRequestSchema = zod.z.object({
96
101
  const evaluatePermissionRequestBatchSchema = zod.z.object({
97
102
  items: zod.z.array(evaluatePermissionRequestSchema)
98
103
  });
99
- const handleRequest = async (requests, user, policy, permissionIntegrationClient, authHeader) => {
104
+ const handleRequest = async (requests, policy, permissionIntegrationClient, credentials, auth, userInfo) => {
100
105
  const applyConditionsLoaderFor = lodash.memoize((pluginId) => {
101
106
  return new DataLoader__default["default"](
102
- (batch) => permissionIntegrationClient.applyConditions(pluginId, batch, authHeader)
107
+ (batch) => permissionIntegrationClient.applyConditions(pluginId, credentials, batch)
103
108
  );
104
109
  });
110
+ let user;
111
+ if (auth.isPrincipal(credentials, "user")) {
112
+ const { ownershipEntityRefs } = await userInfo.getUserInfo(credentials);
113
+ const { token } = await auth.getPluginRequestToken({
114
+ onBehalfOf: credentials,
115
+ targetPluginId: "catalog"
116
+ // TODO: unknown at this point
117
+ });
118
+ user = {
119
+ identity: {
120
+ type: "user",
121
+ userEntityRef: credentials.principal.userEntityRef,
122
+ ownershipEntityRefs
123
+ },
124
+ token
125
+ };
126
+ }
105
127
  return Promise.all(
106
128
  requests.map(
107
129
  ({ id, resourceRef, ...request }) => policy.handle(request, user).then((decision) => {
@@ -137,14 +159,16 @@ const handleRequest = async (requests, user, policy, permissionIntegrationClient
137
159
  );
138
160
  };
139
161
  async function createRouter(options) {
140
- const { policy, discovery, identity, config, logger } = options;
162
+ const { policy, discovery, config, logger } = options;
163
+ const { auth, httpAuth, userInfo } = backendCommon.createLegacyAuthAdapters(options);
141
164
  if (!config.getOptionalBoolean("permission.enabled")) {
142
165
  logger.warn(
143
166
  "Permission backend started with permissions disabled. Enable permissions by setting permission.enabled=true."
144
167
  );
145
168
  }
146
169
  const permissionIntegrationClient = new PermissionIntegrationClient({
147
- discovery
170
+ discovery,
171
+ auth
148
172
  });
149
173
  const router = Router__default["default"]();
150
174
  router.use(express__default["default"].json());
@@ -154,7 +178,9 @@ async function createRouter(options) {
154
178
  router.post(
155
179
  "/authorize",
156
180
  async (req, res) => {
157
- const user = await identity.getIdentity({ request: req });
181
+ const credentials = await httpAuth.credentials(req, {
182
+ allow: ["user", "none"]
183
+ });
158
184
  const parseResult = evaluatePermissionRequestBatchSchema.safeParse(
159
185
  req.body
160
186
  );
@@ -165,10 +191,11 @@ async function createRouter(options) {
165
191
  res.json({
166
192
  items: await handleRequest(
167
193
  body.items,
168
- user,
169
194
  policy,
170
195
  permissionIntegrationClient,
171
- req.header("authorization")
196
+ credentials,
197
+ auth,
198
+ userInfo
172
199
  )
173
200
  });
174
201
  }
@@ -178,4 +205,4 @@ async function createRouter(options) {
178
205
  }
179
206
 
180
207
  exports.createRouter = createRouter;
181
- //# sourceMappingURL=router-40c490a9.cjs.js.map
208
+ //# sourceMappingURL=router-ced2cd8a.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-ced2cd8a.cjs.js","sources":["../../src/service/PermissionIntegrationClient.ts","../../src/service/router.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 fetch from 'node-fetch';\nimport { z } from 'zod';\nimport {\n AuthorizeResult,\n ConditionalPolicyDecision,\n} from '@backstage/plugin-permission-common';\nimport {\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry,\n} from '@backstage/plugin-permission-node';\nimport {\n AuthService,\n BackstageCredentials,\n DiscoveryService,\n} from '@backstage/backend-plugin-api';\n\nconst responseSchema = z.object({\n items: z.array(\n z.object({\n id: z.string(),\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n ),\n});\n\nexport type ResourcePolicyDecision = ConditionalPolicyDecision & {\n resourceRef: string;\n};\n\nexport class PermissionIntegrationClient {\n private readonly discovery: DiscoveryService;\n private readonly auth: AuthService;\n\n constructor(options: { discovery: DiscoveryService; auth: AuthService }) {\n this.discovery = options.discovery;\n this.auth = options.auth;\n }\n\n async applyConditions(\n pluginId: string,\n credentials: BackstageCredentials,\n decisions: readonly ApplyConditionsRequestEntry[],\n ): Promise<ApplyConditionsResponseEntry[]> {\n const baseUrl = await this.discovery.getBaseUrl(pluginId);\n const endpoint = `${baseUrl}/.well-known/backstage/permissions/apply-conditions`;\n\n const token = this.auth.isPrincipal(credentials, 'none')\n ? undefined\n : await this.auth\n .getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: pluginId,\n })\n .then(t => t.token);\n\n const response = await fetch(endpoint, {\n method: 'POST',\n body: JSON.stringify({\n items: decisions.map(\n ({ id, resourceRef, resourceType, conditions }) => ({\n id,\n resourceRef,\n resourceType,\n conditions,\n }),\n ),\n }),\n headers: {\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n 'content-type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Unexpected response from plugin upstream when applying conditions. Expected 200 but got ${response.status} - ${response.statusText}`,\n );\n }\n\n const result = responseSchema.parse(await response.json());\n\n return result.items;\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 { z } from 'zod';\nimport express, { Request, Response } from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport {\n createLegacyAuthAdapters,\n errorHandler,\n} from '@backstage/backend-common';\nimport { InputError } from '@backstage/errors';\nimport {\n BackstageIdentityResponse,\n IdentityApi,\n} from '@backstage/plugin-auth-node';\nimport {\n AuthorizeResult,\n EvaluatePermissionResponse,\n EvaluatePermissionRequest,\n IdentifiedPermissionMessage,\n EvaluatePermissionRequestBatch,\n EvaluatePermissionResponseBatch,\n isResourcePermission,\n PermissionAttributes,\n} from '@backstage/plugin-permission-common';\nimport {\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry,\n PermissionPolicy,\n} from '@backstage/plugin-permission-node';\nimport { PermissionIntegrationClient } from './PermissionIntegrationClient';\nimport { memoize } from 'lodash';\nimport DataLoader from 'dataloader';\nimport { Config } from '@backstage/config';\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstageUserPrincipal,\n DiscoveryService,\n HttpAuthService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\n\nconst attributesSchema: z.ZodSchema<PermissionAttributes> = z.object({\n action: z\n .union([\n z.literal('create'),\n z.literal('read'),\n z.literal('update'),\n z.literal('delete'),\n ])\n .optional(),\n});\n\nconst permissionSchema = z.union([\n z.object({\n type: z.literal('basic'),\n name: z.string(),\n attributes: attributesSchema,\n }),\n z.object({\n type: z.literal('resource'),\n name: z.string(),\n attributes: attributesSchema,\n resourceType: z.string(),\n }),\n]);\n\nconst evaluatePermissionRequestSchema: z.ZodSchema<\n IdentifiedPermissionMessage<EvaluatePermissionRequest>\n> = z.object({\n id: z.string(),\n resourceRef: z.string().optional(),\n permission: permissionSchema,\n});\n\nconst evaluatePermissionRequestBatchSchema: z.ZodSchema<EvaluatePermissionRequestBatch> =\n z.object({\n items: z.array(evaluatePermissionRequestSchema),\n });\n\n/**\n * Options required when constructing a new {@link express#Router} using\n * {@link createRouter}.\n *\n * @public\n */\nexport interface RouterOptions {\n logger: Logger;\n discovery: DiscoveryService;\n policy: PermissionPolicy;\n identity?: IdentityApi;\n config: Config;\n auth?: AuthService;\n httpAuth?: HttpAuthService;\n userInfo?: UserInfoService;\n}\n\nconst handleRequest = async (\n requests: IdentifiedPermissionMessage<EvaluatePermissionRequest>[],\n policy: PermissionPolicy,\n permissionIntegrationClient: PermissionIntegrationClient,\n credentials: BackstageCredentials<\n BackstageNonePrincipal | BackstageUserPrincipal\n >,\n auth: AuthService,\n userInfo: UserInfoService,\n): Promise<IdentifiedPermissionMessage<EvaluatePermissionResponse>[]> => {\n const applyConditionsLoaderFor = memoize((pluginId: string) => {\n return new DataLoader<\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry\n >(batch =>\n permissionIntegrationClient.applyConditions(pluginId, credentials, batch),\n );\n });\n\n let user: BackstageIdentityResponse | undefined;\n if (auth.isPrincipal(credentials, 'user')) {\n const { ownershipEntityRefs } = await userInfo.getUserInfo(credentials);\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog', // TODO: unknown at this point\n });\n user = {\n identity: {\n type: 'user',\n userEntityRef: credentials.principal.userEntityRef,\n ownershipEntityRefs,\n },\n token,\n };\n }\n\n return Promise.all(\n requests.map(({ id, resourceRef, ...request }) =>\n policy.handle(request, user).then(decision => {\n if (decision.result !== AuthorizeResult.CONDITIONAL) {\n return {\n id,\n ...decision,\n };\n }\n\n if (!isResourcePermission(request.permission)) {\n throw new Error(\n `Conditional decision returned from permission policy for non-resource permission ${request.permission.name}`,\n );\n }\n\n if (decision.resourceType !== request.permission.resourceType) {\n throw new Error(\n `Invalid resource conditions returned from permission policy for permission ${request.permission.name}`,\n );\n }\n\n if (!resourceRef) {\n return {\n id,\n ...decision,\n };\n }\n\n return applyConditionsLoaderFor(decision.pluginId).load({\n id,\n resourceRef,\n ...decision,\n });\n }),\n ),\n );\n};\n\n/**\n * Creates a new {@link express#Router} which provides the backend API\n * for the permission system.\n *\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { policy, discovery, config, logger } = options;\n const { auth, httpAuth, userInfo } = createLegacyAuthAdapters(options);\n\n if (!config.getOptionalBoolean('permission.enabled')) {\n logger.warn(\n 'Permission backend started with permissions disabled. Enable permissions by setting permission.enabled=true.',\n );\n }\n\n const permissionIntegrationClient = new PermissionIntegrationClient({\n discovery,\n auth,\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n response.json({ status: 'ok' });\n });\n\n router.post(\n '/authorize',\n async (\n req: Request<EvaluatePermissionRequestBatch>,\n res: Response<EvaluatePermissionResponseBatch>,\n ) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['user', 'none'],\n });\n\n const parseResult = evaluatePermissionRequestBatchSchema.safeParse(\n req.body,\n );\n\n if (!parseResult.success) {\n throw new InputError(parseResult.error.toString());\n }\n\n const body = parseResult.data;\n\n res.json({\n items: await handleRequest(\n body.items,\n policy,\n permissionIntegrationClient,\n credentials,\n auth,\n userInfo,\n ),\n });\n },\n );\n\n router.use(errorHandler());\n\n return router;\n}\n"],"names":["z","AuthorizeResult","fetch","memoize","DataLoader","isResourcePermission","createLegacyAuthAdapters","Router","express","InputError","errorHandler"],"mappings":"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"}
package/dist/index.cjs.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var router = require('./cjs/router-40c490a9.cjs.js');
5
+ var router = require('./cjs/router-ced2cd8a.cjs.js');
6
6
  require('zod');
7
7
  require('express');
8
8
  require('express-promise-router');
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import express from 'express';
2
2
  import { Logger } from 'winston';
3
- import { PluginEndpointDiscovery } from '@backstage/backend-common';
4
3
  import { IdentityApi } from '@backstage/plugin-auth-node';
5
4
  import { PermissionPolicy } from '@backstage/plugin-permission-node';
6
5
  import { Config } from '@backstage/config';
6
+ import { DiscoveryService, AuthService, HttpAuthService, UserInfoService } from '@backstage/backend-plugin-api';
7
7
 
8
8
  /**
9
9
  * Options required when constructing a new {@link express#Router} using
@@ -13,10 +13,13 @@ import { Config } from '@backstage/config';
13
13
  */
14
14
  interface RouterOptions {
15
15
  logger: Logger;
16
- discovery: PluginEndpointDiscovery;
16
+ discovery: DiscoveryService;
17
17
  policy: PermissionPolicy;
18
- identity: IdentityApi;
18
+ identity?: IdentityApi;
19
19
  config: Config;
20
+ auth?: AuthService;
21
+ httpAuth?: HttpAuthService;
22
+ userInfo?: UserInfoService;
20
23
  }
21
24
  /**
22
25
  * Creates a new {@link express#Router} which provides the backend API
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "@backstage/plugin-permission-backend",
3
- "version": "0.5.36",
4
- "main": "./dist/index.cjs.js",
5
- "types": "./dist/index.d.ts",
6
- "license": "Apache-2.0",
3
+ "version": "0.5.37-next.1",
4
+ "backstage": {
5
+ "role": "backend-plugin"
6
+ },
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
+ "homepage": "https://backstage.io",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/backstage/backstage",
14
+ "directory": "plugins/permission-backend"
15
+ },
16
+ "license": "Apache-2.0",
10
17
  "exports": {
11
18
  ".": {
12
19
  "require": "./dist/index.cjs.js",
@@ -20,32 +27,29 @@
20
27
  },
21
28
  "./package.json": "./package.json"
22
29
  },
23
- "backstage": {
24
- "role": "backend-plugin"
25
- },
26
- "homepage": "https://backstage.io",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/backstage/backstage",
30
- "directory": "plugins/permission-backend"
31
- },
30
+ "main": "./dist/index.cjs.js",
31
+ "types": "./dist/index.d.ts",
32
+ "files": [
33
+ "dist",
34
+ "alpha"
35
+ ],
32
36
  "scripts": {
33
- "start": "backstage-cli package start",
34
37
  "build": "backstage-cli package build",
38
+ "clean": "backstage-cli package clean",
35
39
  "lint": "backstage-cli package lint",
36
- "test": "backstage-cli package test",
37
40
  "prepack": "backstage-cli package prepack",
38
41
  "postpack": "backstage-cli package postpack",
39
- "clean": "backstage-cli package clean"
42
+ "start": "backstage-cli package start",
43
+ "test": "backstage-cli package test"
40
44
  },
41
45
  "dependencies": {
42
- "@backstage/backend-common": "^0.21.3",
43
- "@backstage/backend-plugin-api": "^0.6.13",
44
- "@backstage/config": "^1.1.1",
45
- "@backstage/errors": "^1.2.3",
46
- "@backstage/plugin-auth-node": "^0.4.8",
47
- "@backstage/plugin-permission-common": "^0.7.12",
48
- "@backstage/plugin-permission-node": "^0.7.24",
46
+ "@backstage/backend-common": "^0.21.4-next.1",
47
+ "@backstage/backend-plugin-api": "^0.6.14-next.1",
48
+ "@backstage/config": "^1.2.0-next.1",
49
+ "@backstage/errors": "^1.2.4-next.0",
50
+ "@backstage/plugin-auth-node": "^0.4.9-next.1",
51
+ "@backstage/plugin-permission-common": "^0.7.13-next.1",
52
+ "@backstage/plugin-permission-node": "^0.7.25-next.1",
49
53
  "@types/express": "*",
50
54
  "dataloader": "^2.0.0",
51
55
  "express": "^4.17.1",
@@ -57,14 +61,11 @@
57
61
  "zod": "^3.22.4"
58
62
  },
59
63
  "devDependencies": {
60
- "@backstage/cli": "^0.25.2",
64
+ "@backstage/backend-test-utils": "^0.3.4-next.1",
65
+ "@backstage/cli": "^0.25.3-next.1",
61
66
  "@types/lodash": "^4.14.151",
62
67
  "@types/supertest": "^2.0.8",
63
68
  "msw": "^1.0.0",
64
69
  "supertest": "^6.1.6"
65
- },
66
- "files": [
67
- "dist",
68
- "alpha"
69
- ]
70
+ }
70
71
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"router-40c490a9.cjs.js","sources":["../../src/service/PermissionIntegrationClient.ts","../../src/service/router.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 fetch from 'node-fetch';\nimport { z } from 'zod';\nimport { PluginEndpointDiscovery } from '@backstage/backend-common';\nimport {\n AuthorizeResult,\n ConditionalPolicyDecision,\n} from '@backstage/plugin-permission-common';\nimport {\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry,\n} from '@backstage/plugin-permission-node';\n\nconst responseSchema = z.object({\n items: z.array(\n z.object({\n id: z.string(),\n result: z\n .literal(AuthorizeResult.ALLOW)\n .or(z.literal(AuthorizeResult.DENY)),\n }),\n ),\n});\n\nexport type ResourcePolicyDecision = ConditionalPolicyDecision & {\n resourceRef: string;\n};\n\nexport class PermissionIntegrationClient {\n private readonly discovery: PluginEndpointDiscovery;\n\n constructor(options: { discovery: PluginEndpointDiscovery }) {\n this.discovery = options.discovery;\n }\n\n async applyConditions(\n pluginId: string,\n decisions: readonly ApplyConditionsRequestEntry[],\n authHeader?: string,\n ): Promise<ApplyConditionsResponseEntry[]> {\n const endpoint = `${await this.discovery.getBaseUrl(\n pluginId,\n )}/.well-known/backstage/permissions/apply-conditions`;\n\n const response = await fetch(endpoint, {\n method: 'POST',\n body: JSON.stringify({\n items: decisions.map(\n ({ id, resourceRef, resourceType, conditions }) => ({\n id,\n resourceRef,\n resourceType,\n conditions,\n }),\n ),\n }),\n headers: {\n ...(authHeader ? { authorization: authHeader } : {}),\n 'content-type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Unexpected response from plugin upstream when applying conditions. Expected 200 but got ${response.status} - ${response.statusText}`,\n );\n }\n\n const result = responseSchema.parse(await response.json());\n\n return result.items;\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 { z } from 'zod';\nimport express, { Request, Response } from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport {\n errorHandler,\n PluginEndpointDiscovery,\n} from '@backstage/backend-common';\nimport { InputError } from '@backstage/errors';\nimport {\n BackstageIdentityResponse,\n IdentityApi,\n} from '@backstage/plugin-auth-node';\nimport {\n AuthorizeResult,\n EvaluatePermissionResponse,\n EvaluatePermissionRequest,\n IdentifiedPermissionMessage,\n EvaluatePermissionRequestBatch,\n EvaluatePermissionResponseBatch,\n isResourcePermission,\n PermissionAttributes,\n} from '@backstage/plugin-permission-common';\nimport {\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry,\n PermissionPolicy,\n} from '@backstage/plugin-permission-node';\nimport { PermissionIntegrationClient } from './PermissionIntegrationClient';\nimport { memoize } from 'lodash';\nimport DataLoader from 'dataloader';\nimport { Config } from '@backstage/config';\n\nconst attributesSchema: z.ZodSchema<PermissionAttributes> = z.object({\n action: z\n .union([\n z.literal('create'),\n z.literal('read'),\n z.literal('update'),\n z.literal('delete'),\n ])\n .optional(),\n});\n\nconst permissionSchema = z.union([\n z.object({\n type: z.literal('basic'),\n name: z.string(),\n attributes: attributesSchema,\n }),\n z.object({\n type: z.literal('resource'),\n name: z.string(),\n attributes: attributesSchema,\n resourceType: z.string(),\n }),\n]);\n\nconst evaluatePermissionRequestSchema: z.ZodSchema<\n IdentifiedPermissionMessage<EvaluatePermissionRequest>\n> = z.object({\n id: z.string(),\n resourceRef: z.string().optional(),\n permission: permissionSchema,\n});\n\nconst evaluatePermissionRequestBatchSchema: z.ZodSchema<EvaluatePermissionRequestBatch> =\n z.object({\n items: z.array(evaluatePermissionRequestSchema),\n });\n\n/**\n * Options required when constructing a new {@link express#Router} using\n * {@link createRouter}.\n *\n * @public\n */\nexport interface RouterOptions {\n logger: Logger;\n discovery: PluginEndpointDiscovery;\n policy: PermissionPolicy;\n identity: IdentityApi;\n config: Config;\n}\n\nconst handleRequest = async (\n requests: IdentifiedPermissionMessage<EvaluatePermissionRequest>[],\n user: BackstageIdentityResponse | undefined,\n policy: PermissionPolicy,\n permissionIntegrationClient: PermissionIntegrationClient,\n authHeader?: string,\n): Promise<IdentifiedPermissionMessage<EvaluatePermissionResponse>[]> => {\n const applyConditionsLoaderFor = memoize((pluginId: string) => {\n return new DataLoader<\n ApplyConditionsRequestEntry,\n ApplyConditionsResponseEntry\n >(batch =>\n permissionIntegrationClient.applyConditions(pluginId, batch, authHeader),\n );\n });\n\n return Promise.all(\n requests.map(({ id, resourceRef, ...request }) =>\n policy.handle(request, user).then(decision => {\n if (decision.result !== AuthorizeResult.CONDITIONAL) {\n return {\n id,\n ...decision,\n };\n }\n\n if (!isResourcePermission(request.permission)) {\n throw new Error(\n `Conditional decision returned from permission policy for non-resource permission ${request.permission.name}`,\n );\n }\n\n if (decision.resourceType !== request.permission.resourceType) {\n throw new Error(\n `Invalid resource conditions returned from permission policy for permission ${request.permission.name}`,\n );\n }\n\n if (!resourceRef) {\n return {\n id,\n ...decision,\n };\n }\n\n return applyConditionsLoaderFor(decision.pluginId).load({\n id,\n resourceRef,\n ...decision,\n });\n }),\n ),\n );\n};\n\n/**\n * Creates a new {@link express#Router} which provides the backend API\n * for the permission system.\n *\n * @public\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { policy, discovery, identity, config, logger } = options;\n\n if (!config.getOptionalBoolean('permission.enabled')) {\n logger.warn(\n 'Permission backend started with permissions disabled. Enable permissions by setting permission.enabled=true.',\n );\n }\n\n const permissionIntegrationClient = new PermissionIntegrationClient({\n discovery,\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n response.json({ status: 'ok' });\n });\n\n router.post(\n '/authorize',\n async (\n req: Request<EvaluatePermissionRequestBatch>,\n res: Response<EvaluatePermissionResponseBatch>,\n ) => {\n const user = await identity.getIdentity({ request: req });\n\n const parseResult = evaluatePermissionRequestBatchSchema.safeParse(\n req.body,\n );\n\n if (!parseResult.success) {\n throw new InputError(parseResult.error.toString());\n }\n\n const body = parseResult.data;\n\n res.json({\n items: await handleRequest(\n body.items,\n user,\n policy,\n permissionIntegrationClient,\n req.header('authorization'),\n ),\n });\n },\n );\n\n router.use(errorHandler());\n\n return router;\n}\n"],"names":["z","AuthorizeResult","fetch","memoize","DataLoader","isResourcePermission","Router","express","InputError","errorHandler"],"mappings":"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"}