@backstage/backend-defaults 0.17.7-next.2 → 0.17.8
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 +34 -0
- package/config.schema.json +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js +34 -9
- package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js +67 -0
- package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js.map +1 -0
- package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js +3 -0
- package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js +18 -2
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js +2 -1
- package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/auth/helpers.cjs.js +7 -1
- package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +5 -0
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +4 -0
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +8 -1
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +8 -1
- package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +7 -4
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +19 -12
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +2 -1
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js +30 -0
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/dist/urlReader.d.ts +4 -1
- package/package.json +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @backstage/backend-defaults
|
|
2
2
|
|
|
3
|
+
## 0.17.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a8fb34a: Improve action handling consistency.
|
|
8
|
+
- bc80ffd: Improved service credential handling during plugin-to-plugin delegation.
|
|
9
|
+
- 47ddbd8: Improved input validation and path handling for cloud storage URL readers.
|
|
10
|
+
- 14e925c: Improved input validation for cloud storage URL readers.
|
|
11
|
+
- a49804f: Fixed handling of GitLab URLs for instances configured with a relative base path.
|
|
12
|
+
- c83c305: Updated URL reader allow list matching so configured paths match either the exact path or paths below it at a segment boundary.
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
- @backstage/integration@2.1.1
|
|
15
|
+
|
|
16
|
+
## 0.17.7
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 79c1d58: Fixed a bug in the Azure DevOps URL reader where the abort signal was not forwarded to the commits API fetch, causing the fetch to hang indefinitely when a build timeout or cancellation was triggered.
|
|
21
|
+
- b1b6c80: Read-only actions now default to non-destructive when the destructive attribute is omitted.
|
|
22
|
+
- 78bf918: chore(deps): bump `tar` from 7.5.15 to 7.5.21
|
|
23
|
+
- bb98419: TypeScript configuration schema warnings discovered while setting up secret redaction are now reported through the Backstage logger.
|
|
24
|
+
- 74d8cbf: Added a new public `@backstage/backend-defaults/rootSystemMetadata` entrypoint, exporting `rootSystemMetadataServiceFactory` and `DefaultRootSystemMetadataService`. The system metadata service is now registered automatically as a default service, so backends no longer need to add it manually.
|
|
25
|
+
- e56d23c: Throw an error when `payload.uip` is missing in `createLimitedUserToken` instead of constructing an invalid limited token with an undefined signature.
|
|
26
|
+
- Updated dependencies
|
|
27
|
+
- @backstage/backend-app-api@1.7.3
|
|
28
|
+
- @backstage/config-loader@1.11.2
|
|
29
|
+
- @backstage/backend-plugin-api@1.10.0
|
|
30
|
+
- @backstage/plugin-auth-node@0.7.4
|
|
31
|
+
- @backstage/integration@2.1.0
|
|
32
|
+
- @backstage/integration-aws-node@0.2.1
|
|
33
|
+
- @backstage/plugin-permission-common@0.9.10
|
|
34
|
+
- @backstage/plugin-permission-node@0.11.3
|
|
35
|
+
- @backstage/plugin-events-node@0.4.25
|
|
36
|
+
|
|
3
37
|
## 0.17.7-next.2
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/config.schema.json
CHANGED
|
@@ -2307,7 +2307,7 @@
|
|
|
2307
2307
|
"items": {
|
|
2308
2308
|
"type": "string"
|
|
2309
2309
|
},
|
|
2310
|
-
"description": "An optional list of paths.
|
|
2310
|
+
"description": "An optional list of paths. When present, only targets matching an exact path or a path below it at a segment boundary are allowed. For example, `/mydir` allows `/mydir` and `/mydir/a`, but blocks `/mydir2`. A trailing slash excludes the exact path without the slash, so `/mydir/` allows `/mydir/a` but not `/mydir`."
|
|
2311
2311
|
}
|
|
2312
2312
|
},
|
|
2313
2313
|
"required": [
|
|
@@ -6,6 +6,7 @@ var v3 = require('zod/v3');
|
|
|
6
6
|
var zodToJsonSchema = require('zod-to-json-schema');
|
|
7
7
|
var errors = require('@backstage/errors');
|
|
8
8
|
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
9
|
+
var actionFilters = require('./actionFilters.cjs.js');
|
|
9
10
|
|
|
10
11
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
11
12
|
|
|
@@ -17,13 +18,15 @@ class DefaultActionsRegistryService {
|
|
|
17
18
|
logger;
|
|
18
19
|
httpAuth;
|
|
19
20
|
auth;
|
|
21
|
+
config;
|
|
20
22
|
metadata;
|
|
21
23
|
permissions;
|
|
22
24
|
permissionsRegistry;
|
|
23
|
-
constructor(logger, httpAuth, auth, metadata, permissions, permissionsRegistry) {
|
|
25
|
+
constructor(logger, httpAuth, auth, config, metadata, permissions, permissionsRegistry) {
|
|
24
26
|
this.logger = logger;
|
|
25
27
|
this.httpAuth = httpAuth;
|
|
26
28
|
this.auth = auth;
|
|
29
|
+
this.config = config;
|
|
27
30
|
this.metadata = metadata;
|
|
28
31
|
this.permissions = permissions;
|
|
29
32
|
this.permissionsRegistry = permissionsRegistry;
|
|
@@ -32,6 +35,7 @@ class DefaultActionsRegistryService {
|
|
|
32
35
|
httpAuth,
|
|
33
36
|
logger,
|
|
34
37
|
auth,
|
|
38
|
+
config,
|
|
35
39
|
metadata,
|
|
36
40
|
permissions,
|
|
37
41
|
permissionsRegistry
|
|
@@ -40,6 +44,7 @@ class DefaultActionsRegistryService {
|
|
|
40
44
|
logger,
|
|
41
45
|
httpAuth,
|
|
42
46
|
auth,
|
|
47
|
+
config,
|
|
43
48
|
metadata,
|
|
44
49
|
permissions,
|
|
45
50
|
permissionsRegistry
|
|
@@ -50,7 +55,9 @@ class DefaultActionsRegistryService {
|
|
|
50
55
|
router.use("/.backstage/actions/", express.json());
|
|
51
56
|
router.get("/.backstage/actions/v1/actions", async (req, res) => {
|
|
52
57
|
const credentials = await this.httpAuth.credentials(req);
|
|
53
|
-
const entries = Array.from(this.actions.entries())
|
|
58
|
+
const entries = Array.from(this.actions.entries()).filter(
|
|
59
|
+
(entry) => this.isActionAllowed(entry)
|
|
60
|
+
);
|
|
54
61
|
const allowedActions = await this.filterByPermissions(
|
|
55
62
|
entries,
|
|
56
63
|
credentials
|
|
@@ -62,13 +69,7 @@ class DefaultActionsRegistryService {
|
|
|
62
69
|
title: action.title,
|
|
63
70
|
description: action.description,
|
|
64
71
|
pluginId: this.metadata.getId(),
|
|
65
|
-
attributes:
|
|
66
|
-
// Inspired by the @modelcontextprotocol/sdk defaults for the hints.
|
|
67
|
-
// https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812
|
|
68
|
-
destructive: action.attributes?.destructive ?? !action.attributes?.readOnly,
|
|
69
|
-
idempotent: action.attributes?.idempotent ?? false,
|
|
70
|
-
readOnly: action.attributes?.readOnly ?? false
|
|
71
|
-
},
|
|
72
|
+
attributes: this.toActionAttributes(action),
|
|
72
73
|
examples: action.examples,
|
|
73
74
|
schema: {
|
|
74
75
|
input: action.schema?.input ? zodToJsonSchema__default.default(action.schema.input(v3.z)) : zodToJsonSchema__default.default(v3.z.object({})),
|
|
@@ -91,6 +92,9 @@ class DefaultActionsRegistryService {
|
|
|
91
92
|
if (!action) {
|
|
92
93
|
throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`);
|
|
93
94
|
}
|
|
95
|
+
if (!this.isActionAllowed([req.params.actionId, action])) {
|
|
96
|
+
throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`);
|
|
97
|
+
}
|
|
94
98
|
if (action.visibilityPermission) {
|
|
95
99
|
const [decision] = await this.permissions.authorize(
|
|
96
100
|
[{ permission: action.visibilityPermission }],
|
|
@@ -163,6 +167,27 @@ class DefaultActionsRegistryService {
|
|
|
163
167
|
}
|
|
164
168
|
this.actions.set(id, options);
|
|
165
169
|
}
|
|
170
|
+
isActionAllowed([id, action]) {
|
|
171
|
+
const pluginSources = this.config.getOptionalStringArray(
|
|
172
|
+
"backend.actions.pluginSources"
|
|
173
|
+
);
|
|
174
|
+
if (pluginSources && !pluginSources.includes(this.metadata.getId())) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return actionFilters.filterActions(this.config, [
|
|
178
|
+
{
|
|
179
|
+
id,
|
|
180
|
+
attributes: this.toActionAttributes(action)
|
|
181
|
+
}
|
|
182
|
+
]).length === 1;
|
|
183
|
+
}
|
|
184
|
+
toActionAttributes(action) {
|
|
185
|
+
return {
|
|
186
|
+
destructive: action.attributes?.destructive ?? !action.attributes?.readOnly,
|
|
187
|
+
idempotent: action.attributes?.idempotent ?? false,
|
|
188
|
+
readOnly: action.attributes?.readOnly ?? false
|
|
189
|
+
};
|
|
190
|
+
}
|
|
166
191
|
async filterByPermissions(entries, credentials) {
|
|
167
192
|
const permissionedEntries = entries.filter(
|
|
168
193
|
([_, action]) => action.visibilityPermission
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use('/.backstage/actions/', json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries());\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: {\n // Inspired by the @modelcontextprotocol/sdk defaults for the hints.\n // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812\n destructive:\n action.attributes?.destructive ?? !action.attributes?.readOnly,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n },\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n ...(action.schema?.secrets && {\n secrets: zodToJsonSchema(action.schema.secrets(z)),\n }),\n },\n })),\n });\n });\n\n const invokeHandler =\n (opts: { wrapped: boolean }) =>\n async (\n req: import('express').Request,\n res: import('express').Response,\n ) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const rawInput = opts.wrapped ? req.body.input : req.body;\n const rawSecrets = opts.wrapped ? req.body.secrets : undefined;\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(rawInput)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n if (action.schema?.secrets && !rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" requires secrets but none were provided`,\n );\n }\n\n if (!action.schema?.secrets && rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" does not accept secrets`,\n );\n }\n\n const secrets = action.schema?.secrets\n ? action.schema.secrets(z).safeParse(rawSecrets)\n : ({ success: true, data: undefined } as const);\n\n if (!secrets.success) {\n throw new InputError(\n `Invalid secrets for action \"${req.params.actionId}\"`,\n secrets.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n secrets: secrets.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n };\n\n // Deprecated: remove v1 invoke route once all callers have migrated to v2\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n invokeHandler({ wrapped: false }),\n );\n\n router.post(\n '/.backstage/actions/v2/actions/:actionId/invoke',\n invokeHandler({ wrapped: true }),\n );\n\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n TSecretsSchema extends AnyZodObject | undefined = undefined,\n >(\n options: ActionsRegistryActionOptions<\n TInputSchema,\n TOutputSchema,\n TSecretsSchema\n >,\n ): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultActionsRegistryService.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n PermissionsRegistryService,\n PermissionsService,\n PluginMetadataService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport PromiseRouter from 'express-promise-router';\nimport { Router, json } from 'express';\nimport { z, AnyZodObject } from 'zod/v3';\nimport zodToJsonSchema from 'zod-to-json-schema';\nimport type {\n ActionsRegistryActionOptions,\n ActionsRegistryService,\n ActionsServiceAction,\n} from '@backstage/backend-plugin-api/alpha';\nimport { InputError, NotAllowedError, NotFoundError } from '@backstage/errors';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport { filterActions } from './actionFilters';\n\ntype ActionEntry = [string, ActionsRegistryActionOptions<any, any, any>];\n\nexport class DefaultActionsRegistryService implements ActionsRegistryService {\n private actions: Map<string, ActionsRegistryActionOptions<any, any, any>> =\n new Map();\n\n private readonly logger: LoggerService;\n private readonly httpAuth: HttpAuthService;\n private readonly auth: AuthService;\n private readonly config: RootConfigService;\n private readonly metadata: PluginMetadataService;\n private readonly permissions: PermissionsService;\n private readonly permissionsRegistry: PermissionsRegistryService;\n\n private constructor(\n logger: LoggerService,\n httpAuth: HttpAuthService,\n auth: AuthService,\n config: RootConfigService,\n metadata: PluginMetadataService,\n permissions: PermissionsService,\n permissionsRegistry: PermissionsRegistryService,\n ) {\n this.logger = logger;\n this.httpAuth = httpAuth;\n this.auth = auth;\n this.config = config;\n this.metadata = metadata;\n this.permissions = permissions;\n this.permissionsRegistry = permissionsRegistry;\n }\n\n static create({\n httpAuth,\n logger,\n auth,\n config,\n metadata,\n permissions,\n permissionsRegistry,\n }: {\n httpAuth: HttpAuthService;\n logger: LoggerService;\n auth: AuthService;\n config: RootConfigService;\n metadata: PluginMetadataService;\n permissions: PermissionsService;\n permissionsRegistry: PermissionsRegistryService;\n }): DefaultActionsRegistryService {\n return new DefaultActionsRegistryService(\n logger,\n httpAuth,\n auth,\n config,\n metadata,\n permissions,\n permissionsRegistry,\n );\n }\n\n createRouter(): Router {\n const router = PromiseRouter();\n router.use('/.backstage/actions/', json());\n\n router.get('/.backstage/actions/v1/actions', async (req, res) => {\n const credentials = await this.httpAuth.credentials(req);\n const entries = Array.from(this.actions.entries()).filter(entry =>\n this.isActionAllowed(entry),\n );\n\n const allowedActions = await this.filterByPermissions(\n entries,\n credentials,\n );\n\n return res.json({\n actions: allowedActions.map(([id, action]) => ({\n id,\n name: action.name,\n title: action.title,\n description: action.description,\n pluginId: this.metadata.getId(),\n attributes: this.toActionAttributes(action),\n examples: action.examples,\n schema: {\n input: action.schema?.input\n ? zodToJsonSchema(action.schema.input(z))\n : zodToJsonSchema(z.object({})),\n output: action.schema?.output\n ? zodToJsonSchema(action.schema.output(z))\n : zodToJsonSchema(z.object({})),\n ...(action.schema?.secrets && {\n secrets: zodToJsonSchema(action.schema.secrets(z)),\n }),\n },\n })),\n });\n });\n\n const invokeHandler =\n (opts: { wrapped: boolean }) =>\n async (\n req: import('express').Request,\n res: import('express').Response,\n ) => {\n const credentials = await this.httpAuth.credentials(req);\n if (this.auth.isPrincipal(credentials, 'none')) {\n throw new NotAllowedError(\n `Actions must be invoked by an authenticated principal, not an anonymous request`,\n );\n }\n\n const action = this.actions.get(req.params.actionId);\n\n if (!action) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (!this.isActionAllowed([req.params.actionId, action])) {\n throw new NotFoundError(`Action \"${req.params.actionId}\" not found`);\n }\n\n if (action.visibilityPermission) {\n const [decision] = await this.permissions.authorize(\n [{ permission: action.visibilityPermission }],\n { credentials },\n );\n if (decision.result !== AuthorizeResult.ALLOW) {\n throw new NotFoundError(\n `Action \"${req.params.actionId}\" not found`,\n );\n }\n }\n\n const rawInput = opts.wrapped ? req.body.input : req.body;\n const rawSecrets = opts.wrapped ? req.body.secrets : undefined;\n\n const input = action.schema?.input\n ? action.schema.input(z).safeParse(rawInput)\n : ({ success: true, data: undefined } as const);\n\n if (!input.success) {\n throw new InputError(\n `Invalid input to action \"${req.params.actionId}\"`,\n input.error,\n );\n }\n\n if (action.schema?.secrets && !rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" requires secrets but none were provided`,\n );\n }\n\n if (!action.schema?.secrets && rawSecrets) {\n throw new InputError(\n `Action \"${req.params.actionId}\" does not accept secrets`,\n );\n }\n\n const secrets = action.schema?.secrets\n ? action.schema.secrets(z).safeParse(rawSecrets)\n : ({ success: true, data: undefined } as const);\n\n if (!secrets.success) {\n throw new InputError(\n `Invalid secrets for action \"${req.params.actionId}\"`,\n secrets.error,\n );\n }\n\n const result = await action.action({\n input: input.data,\n secrets: secrets.data,\n credentials,\n logger: this.logger,\n });\n\n const output = action.schema?.output\n ? action.schema.output(z).safeParse(result?.output)\n : ({ success: true, data: result?.output } as const);\n\n if (!output.success) {\n throw new InputError(\n `Invalid output from action \"${req.params.actionId}\"`,\n output.error,\n );\n }\n\n res.json({ output: output.data });\n };\n\n // Deprecated: remove v1 invoke route once all callers have migrated to v2\n router.post(\n '/.backstage/actions/v1/actions/:actionId/invoke',\n invokeHandler({ wrapped: false }),\n );\n\n router.post(\n '/.backstage/actions/v2/actions/:actionId/invoke',\n invokeHandler({ wrapped: true }),\n );\n\n return router;\n }\n\n register<\n TInputSchema extends AnyZodObject,\n TOutputSchema extends AnyZodObject,\n TSecretsSchema extends AnyZodObject | undefined = undefined,\n >(\n options: ActionsRegistryActionOptions<\n TInputSchema,\n TOutputSchema,\n TSecretsSchema\n >,\n ): void {\n const id = `${this.metadata.getId()}:${options.name}`;\n\n if (this.actions.has(id)) {\n throw new Error(`Action with id \"${id}\" is already registered`);\n }\n\n if (options.visibilityPermission) {\n this.permissionsRegistry.addPermissions([options.visibilityPermission]);\n }\n\n this.actions.set(id, options);\n }\n\n private isActionAllowed([id, action]: ActionEntry): boolean {\n const pluginSources = this.config.getOptionalStringArray(\n 'backend.actions.pluginSources',\n );\n\n if (pluginSources && !pluginSources.includes(this.metadata.getId())) {\n return false;\n }\n\n return (\n filterActions(this.config, [\n {\n id,\n attributes: this.toActionAttributes(action),\n },\n ]).length === 1\n );\n }\n\n private toActionAttributes(\n action: ActionEntry[1],\n ): ActionsServiceAction['attributes'] {\n // Resolve optional attributes to the values exposed by ActionsService.\n return {\n destructive:\n action.attributes?.destructive ?? !action.attributes?.readOnly,\n idempotent: action.attributes?.idempotent ?? false,\n readOnly: action.attributes?.readOnly ?? false,\n };\n }\n\n private async filterByPermissions(\n entries: ActionEntry[],\n credentials: BackstageCredentials,\n ): Promise<ActionEntry[]> {\n const permissionedEntries = entries.filter(\n ([_, action]) => action.visibilityPermission,\n );\n\n if (permissionedEntries.length === 0) {\n return entries;\n }\n\n const decisions = await this.permissions.authorize(\n permissionedEntries.map(([_, action]) => ({\n permission: action.visibilityPermission!,\n })),\n { credentials },\n );\n\n const deniedIds = new Set(\n permissionedEntries\n .filter((_, index) => decisions[index].result !== AuthorizeResult.ALLOW)\n .map(([id]) => id),\n );\n\n return entries.filter(([id]) => !deniedIds.has(id));\n }\n}\n"],"names":["PromiseRouter","json","zodToJsonSchema","z","NotAllowedError","NotFoundError","AuthorizeResult","InputError","filterActions"],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var minimatch = require('minimatch');
|
|
4
|
+
|
|
5
|
+
const ACTION_ATTRIBUTE_KEYS = [
|
|
6
|
+
"destructive",
|
|
7
|
+
"readOnly",
|
|
8
|
+
"idempotent"
|
|
9
|
+
];
|
|
10
|
+
function parseFilterRules(configArray) {
|
|
11
|
+
return configArray.map((ruleConfig) => {
|
|
12
|
+
const idPattern = ruleConfig.getOptionalString("id");
|
|
13
|
+
const attributesConfig = ruleConfig.getOptionalConfig("attributes");
|
|
14
|
+
const rule = {};
|
|
15
|
+
if (idPattern) {
|
|
16
|
+
rule.idMatcher = new minimatch.Minimatch(idPattern);
|
|
17
|
+
}
|
|
18
|
+
if (attributesConfig) {
|
|
19
|
+
rule.attributes = {};
|
|
20
|
+
for (const key of ACTION_ATTRIBUTE_KEYS) {
|
|
21
|
+
const value = attributesConfig.getOptionalBoolean(key);
|
|
22
|
+
if (value !== void 0) {
|
|
23
|
+
rule.attributes[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return rule;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function matchesRule(action, rule) {
|
|
31
|
+
if (rule.idMatcher && !rule.idMatcher.match(action.id)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (rule.attributes) {
|
|
35
|
+
for (const key of ACTION_ATTRIBUTE_KEYS) {
|
|
36
|
+
const value = rule.attributes[key];
|
|
37
|
+
if (value !== void 0 && action.attributes[key] !== value) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function filterActions(config, actions) {
|
|
45
|
+
const filterConfig = config.getOptionalConfig("backend.actions.filter");
|
|
46
|
+
if (!filterConfig) {
|
|
47
|
+
return actions;
|
|
48
|
+
}
|
|
49
|
+
const includeRules = parseFilterRules(
|
|
50
|
+
filterConfig.getOptionalConfigArray("include") ?? []
|
|
51
|
+
);
|
|
52
|
+
const excludeRules = parseFilterRules(
|
|
53
|
+
filterConfig.getOptionalConfigArray("exclude") ?? []
|
|
54
|
+
);
|
|
55
|
+
return actions.filter((action) => {
|
|
56
|
+
if (excludeRules.some((rule) => matchesRule(action, rule))) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (includeRules.length === 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return includeRules.some((rule) => matchesRule(action, rule));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
exports.filterActions = filterActions;
|
|
67
|
+
//# sourceMappingURL=actionFilters.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionFilters.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/actionFilters.ts"],"sourcesContent":["/*\n * Copyright 2026 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 type { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha';\nimport type { RootConfigService } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { Minimatch } from 'minimatch';\n\ninterface ActionFilterTarget {\n id: string;\n attributes: ActionsServiceAction['attributes'];\n}\n\nconst ACTION_ATTRIBUTE_KEYS = [\n 'destructive',\n 'readOnly',\n 'idempotent',\n] as const;\n\ninterface ActionFilterRule {\n idMatcher?: Minimatch;\n attributes?: Partial<Record<(typeof ACTION_ATTRIBUTE_KEYS)[number], boolean>>;\n}\n\nfunction parseFilterRules(configArray: Array<Config>): Array<ActionFilterRule> {\n return configArray.map(ruleConfig => {\n const idPattern = ruleConfig.getOptionalString('id');\n const attributesConfig = ruleConfig.getOptionalConfig('attributes');\n const rule: ActionFilterRule = {};\n\n if (idPattern) {\n rule.idMatcher = new Minimatch(idPattern);\n }\n\n if (attributesConfig) {\n rule.attributes = {};\n for (const key of ACTION_ATTRIBUTE_KEYS) {\n const value = attributesConfig.getOptionalBoolean(key);\n if (value !== undefined) {\n rule.attributes[key] = value;\n }\n }\n }\n\n return rule;\n });\n}\n\nfunction matchesRule(\n action: ActionFilterTarget,\n rule: ActionFilterRule,\n): boolean {\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n if (rule.attributes) {\n for (const key of ACTION_ATTRIBUTE_KEYS) {\n const value = rule.attributes[key];\n if (value !== undefined && action.attributes[key] !== value) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Applies the configured action include and exclude rules.\n *\n * @param config - Root configuration containing the action filters\n * @param actions - Actions with resolved attributes to filter\n * @returns The actions permitted by the configured filters\n *\n * @remarks\n * Exclude rules take precedence. Without include rules, every action that is\n * not excluded is permitted.\n */\nexport function filterActions<TAction extends ActionFilterTarget>(\n config: RootConfigService,\n actions: Array<TAction>,\n): Array<TAction> {\n const filterConfig = config.getOptionalConfig('backend.actions.filter');\n\n if (!filterConfig) {\n return actions;\n }\n\n const includeRules = parseFilterRules(\n filterConfig.getOptionalConfigArray('include') ?? [],\n );\n const excludeRules = parseFilterRules(\n filterConfig.getOptionalConfigArray('exclude') ?? [],\n );\n\n return actions.filter(action => {\n if (excludeRules.some(rule => matchesRule(action, rule))) {\n return false;\n }\n\n if (includeRules.length === 0) {\n return true;\n }\n\n return includeRules.some(rule => matchesRule(action, rule));\n });\n}\n"],"names":["Minimatch"],"mappings":";;;;AA0BA,MAAM,qBAAA,GAAwB;AAAA,EAC5B,aAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA;AAOA,SAAS,iBAAiB,WAAA,EAAqD;AAC7E,EAAA,OAAO,WAAA,CAAY,IAAI,CAAA,UAAA,KAAc;AACnC,IAAA,MAAM,SAAA,GAAY,UAAA,CAAW,iBAAA,CAAkB,IAAI,CAAA;AACnD,IAAA,MAAM,gBAAA,GAAmB,UAAA,CAAW,iBAAA,CAAkB,YAAY,CAAA;AAClE,IAAA,MAAM,OAAyB,EAAC;AAEhC,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,GAAY,IAAIA,mBAAA,CAAU,SAAS,CAAA;AAAA,IAC1C;AAEA,IAAA,IAAI,gBAAA,EAAkB;AACpB,MAAA,IAAA,CAAK,aAAa,EAAC;AACnB,MAAA,KAAA,MAAW,OAAO,qBAAA,EAAuB;AACvC,QAAA,MAAM,KAAA,GAAQ,gBAAA,CAAiB,kBAAA,CAAmB,GAAG,CAAA;AACrD,QAAA,IAAI,UAAU,MAAA,EAAW;AACvB,UAAA,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,KAAA;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,SAAS,WAAA,CACP,QACA,IAAA,EACS;AACT,EAAA,IAAI,IAAA,CAAK,aAAa,CAAC,IAAA,CAAK,UAAU,KAAA,CAAM,MAAA,CAAO,EAAE,CAAA,EAAG;AACtD,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAK,UAAA,EAAY;AACnB,IAAA,KAAA,MAAW,OAAO,qBAAA,EAAuB;AACvC,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA;AACjC,MAAA,IAAI,UAAU,MAAA,IAAa,MAAA,CAAO,UAAA,CAAW,GAAG,MAAM,KAAA,EAAO;AAC3D,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAaO,SAAS,aAAA,CACd,QACA,OAAA,EACgB;AAChB,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,iBAAA,CAAkB,wBAAwB,CAAA;AAEtE,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAe,gBAAA;AAAA,IACnB,YAAA,CAAa,sBAAA,CAAuB,SAAS,CAAA,IAAK;AAAC,GACrD;AACA,EAAA,MAAM,YAAA,GAAe,gBAAA;AAAA,IACnB,YAAA,CAAa,sBAAA,CAAuB,SAAS,CAAA,IAAK;AAAC,GACrD;AAEA,EAAA,OAAO,OAAA,CAAQ,OAAO,CAAA,MAAA,KAAU;AAC9B,IAAA,IAAI,aAAa,IAAA,CAAK,CAAA,IAAA,KAAQ,YAAY,MAAA,EAAQ,IAAI,CAAC,CAAA,EAAG;AACxD,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,aAAa,IAAA,CAAK,CAAA,IAAA,KAAQ,WAAA,CAAY,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,EAC5D,CAAC,CAAA;AACH;;;;"}
|
|
@@ -12,6 +12,7 @@ const actionsRegistryServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
12
12
|
httpAuth: backendPluginApi.coreServices.httpAuth,
|
|
13
13
|
logger: backendPluginApi.coreServices.logger,
|
|
14
14
|
auth: backendPluginApi.coreServices.auth,
|
|
15
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
15
16
|
permissions: backendPluginApi.coreServices.permissions,
|
|
16
17
|
permissionsRegistry: backendPluginApi.coreServices.permissionsRegistry
|
|
17
18
|
},
|
|
@@ -21,6 +22,7 @@ const actionsRegistryServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
21
22
|
httpAuth,
|
|
22
23
|
logger,
|
|
23
24
|
auth,
|
|
25
|
+
config,
|
|
24
26
|
permissions,
|
|
25
27
|
permissionsRegistry
|
|
26
28
|
}) => {
|
|
@@ -28,6 +30,7 @@ const actionsRegistryServiceFactory = backendPluginApi.createServiceFactory({
|
|
|
28
30
|
httpAuth,
|
|
29
31
|
logger,
|
|
30
32
|
auth,
|
|
33
|
+
config,
|
|
31
34
|
metadata,
|
|
32
35
|
permissions,
|
|
33
36
|
permissionsRegistry
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionsRegistryServiceFactory.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultActionsRegistryService } from './DefaultActionsRegistryService';\nimport { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\n\n/**\n * @public\n */\nexport const actionsRegistryServiceFactory = createServiceFactory({\n service: actionsRegistryServiceRef,\n deps: {\n metadata: coreServices.pluginMetadata,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n auth: coreServices.auth,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n },\n factory: ({\n metadata,\n httpRouter,\n httpAuth,\n logger,\n auth,\n permissions,\n permissionsRegistry,\n }) => {\n const actionsRegistryService = DefaultActionsRegistryService.create({\n httpAuth,\n logger,\n auth,\n metadata,\n permissions,\n permissionsRegistry,\n });\n\n httpRouter.use(actionsRegistryService.createRouter());\n\n return actionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","actionsRegistryServiceRef","coreServices","DefaultActionsRegistryService"],"mappings":";;;;;;AA0BO,MAAM,gCAAgCA,qCAAA,CAAqB;AAAA,EAChE,OAAA,EAASC,+BAAA;AAAA,EACT,IAAA,EAAM;AAAA,IACJ,UAAUC,6BAAA,CAAa,cAAA;AAAA,IACvB,YAAYA,6BAAA,CAAa,UAAA;AAAA,IACzB,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,MAAMA,6BAAA,CAAa,IAAA;AAAA,IACnB,aAAaA,6BAAA,CAAa,WAAA;AAAA,IAC1B,qBAAqBA,6BAAA,CAAa;AAAA,GACpC;AAAA,EACA,SAAS,CAAC;AAAA,IACR,QAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,sBAAA,GAAyBC,4DAA8B,MAAA,CAAO;AAAA,MAClE,QAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA;AAAA,MACA,QAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,UAAA,CAAW,GAAA,CAAI,sBAAA,CAAuB,YAAA,EAAc,CAAA;AAEpD,IAAA,OAAO,sBAAA;AAAA,EACT;AACF,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"actionsRegistryServiceFactory.cjs.js","sources":["../../../../src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultActionsRegistryService } from './DefaultActionsRegistryService';\nimport { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';\n\n/**\n * @public\n */\nexport const actionsRegistryServiceFactory = createServiceFactory({\n service: actionsRegistryServiceRef,\n deps: {\n metadata: coreServices.pluginMetadata,\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n permissions: coreServices.permissions,\n permissionsRegistry: coreServices.permissionsRegistry,\n },\n factory: ({\n metadata,\n httpRouter,\n httpAuth,\n logger,\n auth,\n config,\n permissions,\n permissionsRegistry,\n }) => {\n const actionsRegistryService = DefaultActionsRegistryService.create({\n httpAuth,\n logger,\n auth,\n config,\n metadata,\n permissions,\n permissionsRegistry,\n });\n\n httpRouter.use(actionsRegistryService.createRouter());\n\n return actionsRegistryService;\n },\n});\n"],"names":["createServiceFactory","actionsRegistryServiceRef","coreServices","DefaultActionsRegistryService"],"mappings":";;;;;;AA0BO,MAAM,gCAAgCA,qCAAA,CAAqB;AAAA,EAChE,OAAA,EAASC,+BAAA;AAAA,EACT,IAAA,EAAM;AAAA,IACJ,UAAUC,6BAAA,CAAa,cAAA;AAAA,IACvB,YAAYA,6BAAA,CAAa,UAAA;AAAA,IACzB,UAAUA,6BAAA,CAAa,QAAA;AAAA,IACvB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,MAAMA,6BAAA,CAAa,IAAA;AAAA,IACnB,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,aAAaA,6BAAA,CAAa,WAAA;AAAA,IAC1B,qBAAqBA,6BAAA,CAAa;AAAA,GACpC;AAAA,EACA,SAAS,CAAC;AAAA,IACR,QAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,sBAAA,GAAyBC,4DAA8B,MAAA,CAAO;AAAA,MAClE,QAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,UAAA,CAAW,GAAA,CAAI,sBAAA,CAAuB,YAAA,EAAc,CAAA;AAEpD,IAAA,OAAO,sBAAA;AAAA,EACT;AACF,CAAC;;;;"}
|
|
@@ -56,7 +56,8 @@ class DefaultAuthService {
|
|
|
56
56
|
return helpers.createCredentialsWithServicePrincipal(
|
|
57
57
|
externalResult.subject,
|
|
58
58
|
void 0,
|
|
59
|
-
externalResult.accessRestrictions
|
|
59
|
+
externalResult.accessRestrictions,
|
|
60
|
+
externalResult.allAccessRestrictions
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
63
|
throw new errors.AuthenticationError("Illegal token");
|
|
@@ -86,11 +87,26 @@ class DefaultAuthService {
|
|
|
86
87
|
}
|
|
87
88
|
switch (type) {
|
|
88
89
|
// TODO: Check whether the principal is ourselves
|
|
89
|
-
case "service":
|
|
90
|
+
case "service": {
|
|
91
|
+
const servicePrincipal = internalForward.principal;
|
|
92
|
+
if (servicePrincipal.accessRestrictions) {
|
|
93
|
+
const targetRestrictions = internalForward.allAccessRestrictions?.get(targetPluginId);
|
|
94
|
+
if (!targetRestrictions) {
|
|
95
|
+
throw new errors.NotAllowedError(
|
|
96
|
+
`Access to target plugin '${targetPluginId}' is not included in token's access restrictions`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (targetRestrictions.permissionNames?.length || targetRestrictions.permissionAttributes) {
|
|
100
|
+
throw new errors.NotAllowedError(
|
|
101
|
+
`Access to target plugin '${targetPluginId}' is restricted and cannot be delegated`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
90
105
|
return this.pluginTokenHandler.issueToken({
|
|
91
106
|
pluginId: this.pluginId,
|
|
92
107
|
targetPluginId
|
|
93
108
|
});
|
|
109
|
+
}
|
|
94
110
|
case "user": {
|
|
95
111
|
const { token } = internalForward;
|
|
96
112
|
if (!token) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n private readonly userTokenHandler: UserTokenHandler;\n private readonly pluginTokenHandler: PluginTokenHandler;\n private readonly externalTokenHandler: ExternalAuthTokenHandler;\n private readonly pluginId: string;\n private readonly disableDefaultAuthPolicy: boolean;\n private readonly pluginKeySource: PluginKeySource;\n\n constructor(\n userTokenHandler: UserTokenHandler,\n pluginTokenHandler: PluginTokenHandler,\n externalTokenHandler: ExternalAuthTokenHandler,\n pluginId: string,\n disableDefaultAuthPolicy: boolean,\n pluginKeySource: PluginKeySource,\n ) {\n this.userTokenHandler = userTokenHandler;\n this.pluginTokenHandler = pluginTokenHandler;\n this.externalTokenHandler = externalTokenHandler;\n this.pluginId = pluginId;\n this.disableDefaultAuthPolicy = disableDefaultAuthPolicy;\n this.pluginKeySource = pluginKeySource;\n }\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service':\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","decodeJwt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultAuthService.cjs.js","sources":["../../../src/entrypoints/auth/DefaultAuthService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalTypes,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { decodeJwt } from 'jose';\nimport { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler';\nimport {\n createCredentialsWithNonePrincipal,\n createCredentialsWithServicePrincipal,\n createCredentialsWithUserPrincipal,\n toInternalBackstageCredentials,\n} from './helpers';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { PluginKeySource } from './plugin/keys/types';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/** @internal */\nexport class DefaultAuthService implements AuthService {\n private readonly userTokenHandler: UserTokenHandler;\n private readonly pluginTokenHandler: PluginTokenHandler;\n private readonly externalTokenHandler: ExternalAuthTokenHandler;\n private readonly pluginId: string;\n private readonly disableDefaultAuthPolicy: boolean;\n private readonly pluginKeySource: PluginKeySource;\n\n constructor(\n userTokenHandler: UserTokenHandler,\n pluginTokenHandler: PluginTokenHandler,\n externalTokenHandler: ExternalAuthTokenHandler,\n pluginId: string,\n disableDefaultAuthPolicy: boolean,\n pluginKeySource: PluginKeySource,\n ) {\n this.userTokenHandler = userTokenHandler;\n this.pluginTokenHandler = pluginTokenHandler;\n this.externalTokenHandler = externalTokenHandler;\n this.pluginId = pluginId;\n this.disableDefaultAuthPolicy = disableDefaultAuthPolicy;\n this.pluginKeySource = pluginKeySource;\n }\n\n async authenticate(\n token: string,\n options?: {\n allowLimitedAccess?: boolean;\n },\n ): Promise<BackstageCredentials> {\n const pluginResult = await this.pluginTokenHandler.verifyToken(token);\n if (pluginResult) {\n if (pluginResult.limitedUserToken) {\n const userResult = await this.userTokenHandler.verifyToken(\n pluginResult.limitedUserToken,\n );\n if (!userResult) {\n throw new AuthenticationError(\n 'Invalid user token in plugin token obo claim',\n );\n }\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n pluginResult.limitedUserToken,\n this.#getJwtExpiration(pluginResult.limitedUserToken),\n pluginResult.subject,\n );\n }\n return createCredentialsWithServicePrincipal(pluginResult.subject);\n }\n\n const userResult = await this.userTokenHandler.verifyToken(token);\n if (userResult) {\n if (\n !options?.allowLimitedAccess &&\n this.userTokenHandler.isLimitedUserToken(token)\n ) {\n throw new AuthenticationError('Illegal limited user token');\n }\n\n return createCredentialsWithUserPrincipal(\n userResult.userEntityRef,\n token,\n this.#getJwtExpiration(token),\n );\n }\n\n const externalResult = await this.externalTokenHandler.verifyToken(token);\n if (externalResult) {\n return createCredentialsWithServicePrincipal(\n externalResult.subject,\n undefined,\n externalResult.accessRestrictions,\n externalResult.allAccessRestrictions,\n );\n }\n\n throw new AuthenticationError('Illegal token');\n }\n\n isPrincipal<TType extends keyof BackstagePrincipalTypes>(\n credentials: BackstageCredentials,\n type: TType,\n ): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {\n const principal = credentials.principal as\n | BackstageUserPrincipal\n | BackstageServicePrincipal;\n\n if (type === 'unknown') {\n return true;\n }\n\n if (principal.type !== type) {\n return false;\n }\n\n return true;\n }\n\n async getNoneCredentials(): Promise<\n BackstageCredentials<BackstageNonePrincipal>\n > {\n return createCredentialsWithNonePrincipal();\n }\n\n async getOwnServiceCredentials(): Promise<\n BackstageCredentials<BackstageServicePrincipal>\n > {\n return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`);\n }\n\n async getPluginRequestToken(options: {\n onBehalfOf: BackstageCredentials;\n targetPluginId: string;\n }): Promise<{ token: string }> {\n const { targetPluginId } = options;\n const internalForward = toInternalBackstageCredentials(options.onBehalfOf);\n const { type } = internalForward.principal;\n\n // Since disabling the default policy means we'll be allowing\n // unauthenticated requests through, we might have unauthenticated\n // credentials from service calls that reach this point. If that's the case,\n // we'll want to keep \"forwarding\" the unauthenticated credentials, which we\n // do by returning an empty token.\n if (type === 'none' && this.disableDefaultAuthPolicy) {\n return { token: '' };\n }\n\n // check whether a plugin support the new auth system\n // by checking the public keys endpoint existence.\n switch (type) {\n // TODO: Check whether the principal is ourselves\n case 'service': {\n const servicePrincipal =\n internalForward.principal as BackstageServicePrincipal;\n if (servicePrincipal.accessRestrictions) {\n const targetRestrictions =\n internalForward.allAccessRestrictions?.get(targetPluginId);\n if (!targetRestrictions) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is not included in token's access restrictions`,\n );\n }\n if (\n targetRestrictions.permissionNames?.length ||\n targetRestrictions.permissionAttributes\n ) {\n throw new NotAllowedError(\n `Access to target plugin '${targetPluginId}' is restricted and cannot be delegated`,\n );\n }\n }\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n });\n }\n case 'user': {\n const { token } = internalForward;\n if (!token) {\n throw new Error('User credentials is unexpectedly missing token');\n }\n const onBehalfOf = await this.userTokenHandler.createLimitedUserToken(\n token,\n );\n return this.pluginTokenHandler.issueToken({\n pluginId: this.pluginId,\n targetPluginId,\n onBehalfOf: {\n limitedUserToken: onBehalfOf.token,\n expiresAt: onBehalfOf.expiresAt,\n },\n });\n }\n default:\n throw new AuthenticationError(\n `Refused to issue service token for credential type '${type}'`,\n );\n }\n }\n\n async getLimitedUserToken(\n credentials: BackstageCredentials<BackstageUserPrincipal>,\n ): Promise<{ token: string; expiresAt: Date }> {\n const { token: backstageToken } =\n toInternalBackstageCredentials(credentials);\n if (!backstageToken) {\n throw new AuthenticationError(\n 'User credentials is unexpectedly missing token',\n );\n }\n\n return this.userTokenHandler.createLimitedUserToken(backstageToken);\n }\n\n async listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> {\n const { keys } = await this.pluginKeySource.listKeys();\n return { keys: keys.map(({ key }) => key) };\n }\n\n #getJwtExpiration(token: string) {\n const { exp } = decodeJwt(token);\n if (!exp) {\n throw new AuthenticationError('User token is missing expiration');\n }\n return new Date(exp * 1000);\n }\n}\n"],"names":["userResult","AuthenticationError","createCredentialsWithUserPrincipal","createCredentialsWithServicePrincipal","createCredentialsWithNonePrincipal","toInternalBackstageCredentials","NotAllowedError","decodeJwt"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExternalAuthTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalAuthTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstagePrincipalAccessRestrictions,\n createServiceRef,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { legacyTokenHandler } from './legacy';\nimport { staticTokenHandler } from './static';\nimport { jwksTokenHandler } from './jwks';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\nimport { readAccessRestrictionsFromConfig } from './helpers';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * @public\n * This service is used to add custom handlers for external token.\n */\nexport const externalTokenHandlersServiceRef = createServiceRef<\n ExternalTokenHandler<unknown>\n>({\n id: 'core.auth.externalTokenHandlers',\n multiton: true,\n});\n\nconst defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {\n static: staticTokenHandler,\n legacy: legacyTokenHandler,\n jwks: jwksTokenHandler,\n};\n\ntype ContextMapEntry<T> = {\n context: T;\n handler: ExternalTokenHandler<T>;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalAuthTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n externalTokenHandlers?: ExternalTokenHandler<unknown>[];\n }): ExternalAuthTokenHandler {\n const {\n ownPluginId,\n config,\n externalTokenHandlers: customHandlers = [],\n logger,\n } = options;\n\n const handlersTypes = customHandlers.reduce<\n Record<string, ExternalTokenHandler<unknown>>\n >(\n (acc, handler) => {\n if (acc[handler.type]) {\n throw new Error(\n `Duplicate external token handler type '${handler.type}', each handler must have a unique type`,\n );\n }\n acc[handler.type] = handler;\n return acc;\n },\n { ...defaultHandlers },\n );\n\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(\n handlerConfig => {\n const type = handlerConfig.getString('type');\n\n const handler = handlersTypes[type];\n if (!handler) {\n const valid = Object.keys(handlersTypes)\n .map(h => `'${h}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n return {\n context: handler.initialize({\n options: handlerConfig.getConfig('options'),\n }),\n handler,\n allAccessRestrictions: handlerConfig\n ? readAccessRestrictionsFromConfig(handlerConfig)\n : undefined,\n };\n },\n );\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n\n for (const legacyConfig of legacyConfigs) {\n contexts.push({\n context: legacyTokenHandler.initialize({\n legacy: true,\n options: legacyConfig,\n }),\n handler: legacyTokenHandler,\n allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig),\n });\n }\n\n return new ExternalAuthTokenHandler(ownPluginId, contexts);\n }\n\n private readonly ownPluginId: string;\n private readonly contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[];\n\n constructor(\n ownPluginId: string,\n contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[],\n ) {\n this.ownPluginId = ownPluginId;\n this.contexts = contexts;\n }\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n }\n | undefined\n > {\n for (const { handler, allAccessRestrictions, context } of this.contexts) {\n const result = await handler.verifyToken(token, context);\n if (result) {\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...result,\n accessRestrictions,\n };\n }\n\n return result;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["createServiceRef","staticTokenHandler","legacyTokenHandler","jwksTokenHandler","readAccessRestrictionsFromConfig","NotAllowedError"],"mappings":";;;;;;;;;AA6BA,MAAM,cAAA,GAAiB,6BAAA;AACvB,MAAM,cAAA,GAAiB,mBAAA;AACvB,IAAI,wBAAA,GAA2B,KAAA;AAMxB,MAAM,kCAAkCA,iCAAA,CAE7C;AAAA,EACA,EAAA,EAAI,iCAAA;AAAA,EACJ,QAAA,EAAU;AACZ,CAAC;AAED,MAAM,eAAA,GAAiE;AAAA,EACrE,MAAA,EAAQC,0BAAA;AAAA,EACR,MAAA,EAAQC,yBAAA;AAAA,EACR,IAAA,EAAMC;AACR,CAAA;AAcO,MAAM,wBAAA,CAAyB;AAAA,EACpC,OAAO,OAAO,OAAA,EAKe;AAC3B,IAAA,MAAM;AAAA,MACJ,WAAA;AAAA,MACA,MAAA;AAAA,MACA,qBAAA,EAAuB,iBAAiB,EAAC;AAAA,MACzC;AAAA,KACF,GAAI,OAAA;AAEJ,IAAA,MAAM,gBAAgB,cAAA,CAAe,MAAA;AAAA,MAGnC,CAAC,KAAK,OAAA,KAAY;AAChB,QAAA,IAAI,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,uCAAA,EAA0C,QAAQ,IAAI,CAAA,uCAAA;AAAA,WACxD;AAAA,QACF;AACA,QAAA,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,GAAI,OAAA;AACpB,QAAA,OAAO,GAAA;AAAA,MACT,CAAA;AAAA,MACA,EAAE,GAAG,eAAA;AAAgB,KACvB;AAEA,IAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,sBAAA,CAAuB,cAAc,KAAK,EAAC;AACzE,IAAA,MAAM,WAAuC,cAAA,CAAe,GAAA;AAAA,MAC1D,CAAA,aAAA,KAAiB;AACf,QAAA,MAAM,IAAA,GAAO,aAAA,CAAc,SAAA,CAAU,MAAM,CAAA;AAE3C,QAAA,MAAM,OAAA,GAAU,cAAc,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,OAAA,EAAS;AACZ,UAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,aAAa,CAAA,CACpC,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CACjB,IAAA,CAAK,IAAI,CAAA;AACZ,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,cAAA,EAAiB,IAAI,CAAA,KAAA,EAAQ,cAAc,qBAAqB,KAAK,CAAA;AAAA,WACvE;AAAA,QACF;AACA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,QAAQ,UAAA,CAAW;AAAA,YAC1B,OAAA,EAAS,aAAA,CAAc,SAAA,CAAU,SAAS;AAAA,WAC3C,CAAA;AAAA,UACD,OAAA;AAAA,UACA,qBAAA,EAAuB,aAAA,GACnBC,wCAAA,CAAiC,aAAa,CAAA,GAC9C;AAAA,SACN;AAAA,MACF;AAAA,KACF;AAGA,IAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,sBAAA,CAAuB,cAAc,KAAK,EAAC;AAExE,IAAA,IAAI,aAAA,CAAc,MAAA,IAAU,CAAC,wBAAA,EAA0B;AACrD,MAAA,wBAAA,GAA2B,IAAA;AAE3B,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,CAAA,yBAAA,EAA4B,cAAc,CAAA,6BAAA,EAAgC,cAAc,CAAA,4DAAA;AAAA,OAC1F;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,gBAAgB,aAAA,EAAe;AACxC,MAAA,QAAA,CAAS,IAAA,CAAK;AAAA,QACZ,OAAA,EAASF,0BAAmB,UAAA,CAAW;AAAA,UACrC,MAAA,EAAQ,IAAA;AAAA,UACR,OAAA,EAAS;AAAA,SACV,CAAA;AAAA,QACD,OAAA,EAASA,yBAAA;AAAA,QACT,qBAAA,EAAuBE,yCAAiC,YAAY;AAAA,OACrE,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,IAAI,wBAAA,CAAyB,WAAA,EAAa,QAAQ,CAAA;AAAA,EAC3D;AAAA,EAEiB,WAAA;AAAA,EACA,QAAA;AAAA,EAMjB,WAAA,CACE,aACA,QAAA,EAKA;AACA,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AACnB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA,EAEA,MAAM,YAAY,KAAA,
|
|
1
|
+
{"version":3,"file":"ExternalAuthTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalAuthTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstagePrincipalAccessRestrictions,\n createServiceRef,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { legacyTokenHandler } from './legacy';\nimport { staticTokenHandler } from './static';\nimport { jwksTokenHandler } from './jwks';\nimport { AccessRestrictionsMap, ExternalTokenHandler } from './types';\nimport { readAccessRestrictionsFromConfig } from './helpers';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * @public\n * This service is used to add custom handlers for external token.\n */\nexport const externalTokenHandlersServiceRef = createServiceRef<\n ExternalTokenHandler<unknown>\n>({\n id: 'core.auth.externalTokenHandlers',\n multiton: true,\n});\n\nconst defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {\n static: staticTokenHandler,\n legacy: legacyTokenHandler,\n jwks: jwksTokenHandler,\n};\n\ntype ContextMapEntry<T> = {\n context: T;\n handler: ExternalTokenHandler<T>;\n allAccessRestrictions?: AccessRestrictionsMap;\n};\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalAuthTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n externalTokenHandlers?: ExternalTokenHandler<unknown>[];\n }): ExternalAuthTokenHandler {\n const {\n ownPluginId,\n config,\n externalTokenHandlers: customHandlers = [],\n logger,\n } = options;\n\n const handlersTypes = customHandlers.reduce<\n Record<string, ExternalTokenHandler<unknown>>\n >(\n (acc, handler) => {\n if (acc[handler.type]) {\n throw new Error(\n `Duplicate external token handler type '${handler.type}', each handler must have a unique type`,\n );\n }\n acc[handler.type] = handler;\n return acc;\n },\n { ...defaultHandlers },\n );\n\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(\n handlerConfig => {\n const type = handlerConfig.getString('type');\n\n const handler = handlersTypes[type];\n if (!handler) {\n const valid = Object.keys(handlersTypes)\n .map(h => `'${h}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n return {\n context: handler.initialize({\n options: handlerConfig.getConfig('options'),\n }),\n handler,\n allAccessRestrictions: handlerConfig\n ? readAccessRestrictionsFromConfig(handlerConfig)\n : undefined,\n };\n },\n );\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n\n for (const legacyConfig of legacyConfigs) {\n contexts.push({\n context: legacyTokenHandler.initialize({\n legacy: true,\n options: legacyConfig,\n }),\n handler: legacyTokenHandler,\n allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig),\n });\n }\n\n return new ExternalAuthTokenHandler(ownPluginId, contexts);\n }\n\n private readonly ownPluginId: string;\n private readonly contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[];\n\n constructor(\n ownPluginId: string,\n contexts: {\n context: unknown;\n handler: ExternalTokenHandler<unknown>;\n allAccessRestrictions?: AccessRestrictionsMap;\n }[],\n ) {\n this.ownPluginId = ownPluginId;\n this.contexts = contexts;\n }\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n allAccessRestrictions?: AccessRestrictionsMap;\n }\n | undefined\n > {\n for (const { handler, allAccessRestrictions, context } of this.contexts) {\n const result = await handler.verifyToken(token, context);\n if (result) {\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...result,\n accessRestrictions,\n allAccessRestrictions,\n };\n }\n\n return result;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["createServiceRef","staticTokenHandler","legacyTokenHandler","jwksTokenHandler","readAccessRestrictionsFromConfig","NotAllowedError"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var node_crypto = require('node:crypto');
|
|
4
4
|
|
|
5
|
-
function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
|
|
5
|
+
function createCredentialsWithServicePrincipal(sub, token, accessRestrictions, allAccessRestrictions) {
|
|
6
6
|
const principal = createServicePrincipal(sub, accessRestrictions);
|
|
7
7
|
const result = {
|
|
8
8
|
$$type: "@backstage/BackstageCredentials",
|
|
@@ -16,6 +16,12 @@ function createCredentialsWithServicePrincipal(sub, token, accessRestrictions) {
|
|
|
16
16
|
writable: true,
|
|
17
17
|
value: token
|
|
18
18
|
},
|
|
19
|
+
allAccessRestrictions: {
|
|
20
|
+
enumerable: false,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: allAccessRestrictions
|
|
24
|
+
},
|
|
19
25
|
toString: {
|
|
20
26
|
enumerable: false,
|
|
21
27
|
configurable: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\nimport { createHash } from 'node:crypto';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n const principal = createServicePrincipal(sub, accessRestrictions);\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n const principal = createUserPrincipal(\n sub,\n actor ? createServicePrincipal(actor) : undefined,\n );\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n const principal = createNonePrincipal();\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n\nfunction createServicePrincipal(\n sub: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): BackstageServicePrincipal {\n const result = {\n type: 'service',\n subject: sub,\n accessRestrictions,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = sub;\n if (accessRestrictions) {\n const hash = createHash('sha256')\n .update(JSON.stringify(accessRestrictions))\n .digest('base64')\n .replace(/=+$/, '');\n parts += `,accessRestrictions=${hash}`;\n }\n return `servicePrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createUserPrincipal(\n userEntityRef: string,\n actor?: BackstageServicePrincipal,\n): BackstageUserPrincipal {\n const result = {\n type: 'user',\n userEntityRef,\n actor,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = userEntityRef;\n if (actor) {\n parts += `,actor={${actor}}`;\n }\n return `userPrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createNonePrincipal(): BackstageNonePrincipal {\n const result = {\n type: 'none',\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => 'nonePrincipal',\n },\n });\n return result;\n}\n"],"names":["createHash"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\nimport { AccessRestrictionsMap } from './external/types';\nimport { createHash } from 'node:crypto';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n allAccessRestrictions?: AccessRestrictionsMap,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n const principal = createServicePrincipal(sub, accessRestrictions);\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n allAccessRestrictions: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: allAccessRestrictions,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n actor?: string,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n const principal = createUserPrincipal(\n sub,\n actor ? createServicePrincipal(actor) : undefined,\n );\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n expiresAt,\n principal,\n } as const;\n Object.defineProperties(result, {\n token: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: token,\n },\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n const principal = createNonePrincipal();\n const result = {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => `backstageCredentials{${principal}}`,\n },\n });\n return result;\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n\nfunction createServicePrincipal(\n sub: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): BackstageServicePrincipal {\n const result = {\n type: 'service',\n subject: sub,\n accessRestrictions,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = sub;\n if (accessRestrictions) {\n const hash = createHash('sha256')\n .update(JSON.stringify(accessRestrictions))\n .digest('base64')\n .replace(/=+$/, '');\n parts += `,accessRestrictions=${hash}`;\n }\n return `servicePrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createUserPrincipal(\n userEntityRef: string,\n actor?: BackstageServicePrincipal,\n): BackstageUserPrincipal {\n const result = {\n type: 'user',\n userEntityRef,\n actor,\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => {\n let parts = userEntityRef;\n if (actor) {\n parts += `,actor={${actor}}`;\n }\n return `userPrincipal{${parts}}`;\n },\n },\n });\n return result;\n}\n\nfunction createNonePrincipal(): BackstageNonePrincipal {\n const result = {\n type: 'none',\n } as const;\n Object.defineProperties(result, {\n toString: {\n enumerable: false,\n configurable: true,\n writable: true,\n value: () => 'nonePrincipal',\n },\n });\n return result;\n}\n"],"names":["createHash"],"mappings":"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"}
|
|
@@ -81,6 +81,11 @@ class UserTokenHandler {
|
|
|
81
81
|
"Failed to create limited user token, invalid token type"
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
|
+
if (!payload.uip) {
|
|
85
|
+
throw new errors.AuthenticationError(
|
|
86
|
+
"Failed to create limited user token, missing user identity proof"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
84
89
|
const limitedUserToken = [
|
|
85
90
|
jose.base64url.encode(
|
|
86
91
|
JSON.stringify({
|