@red-hat-developer-hub/backstage-plugin-extensions-backend 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+ var FileInstallationStorage = require('./FileInstallationStorage.cjs.js');
5
+ var InstallationInitError = require('../errors/InstallationInitError.cjs.js');
6
+ var ConfigFormatError = require('../errors/ConfigFormatError.cjs.js');
7
+
8
+ class InstallationDataService {
9
+ constructor(extensionsApi, _installationStorage, initializationError) {
10
+ this.extensionsApi = extensionsApi;
11
+ this._installationStorage = _installationStorage;
12
+ this.initializationError = initializationError;
13
+ }
14
+ get installationStorage() {
15
+ if (!this._installationStorage) {
16
+ throw new Error("Installation storage is not initialized", {
17
+ cause: this.initializationError
18
+ });
19
+ }
20
+ return this._installationStorage;
21
+ }
22
+ static fromConfig(deps) {
23
+ const { config, extensionsApi, logger } = deps;
24
+ const serviceWithInitializationError = (reason, message, cause) => {
25
+ if (reason === InstallationInitError.InstallationInitErrorReason.INSTALLATION_DISABLED_IN_PRODUCTION || reason === InstallationInitError.InstallationInitErrorReason.INSTALLATION_DISABLED) {
26
+ logger.warn(message);
27
+ } else {
28
+ logger.error(
29
+ `Installation feature is disabled. Error while loading data: ${message}`
30
+ );
31
+ }
32
+ return new InstallationDataService(
33
+ extensionsApi,
34
+ undefined,
35
+ new InstallationInitError.InstallationInitError(reason, message, cause)
36
+ );
37
+ };
38
+ try {
39
+ const node_env = process.env.NODE_ENV ?? "development";
40
+ if (node_env === "production") {
41
+ return serviceWithInitializationError(
42
+ InstallationInitError.InstallationInitErrorReason.INSTALLATION_DISABLED_IN_PRODUCTION,
43
+ "Installation feature is disabled in production"
44
+ );
45
+ }
46
+ const installationEnabled = config.getOptionalBoolean(
47
+ "extensions.installation.enabled"
48
+ );
49
+ if (!installationEnabled) {
50
+ return serviceWithInitializationError(
51
+ InstallationInitError.InstallationInitErrorReason.INSTALLATION_DISABLED,
52
+ "Installation feature is disabled under 'extensions.installation.enabled'"
53
+ );
54
+ }
55
+ const filePath = config.getOptionalString(
56
+ "extensions.installation.saveToSingleFile.file"
57
+ );
58
+ if (!filePath) {
59
+ return serviceWithInitializationError(
60
+ InstallationInitError.InstallationInitErrorReason.FILE_CONFIG_VALUE_MISSING,
61
+ "The 'extensions.installation.saveToSingleFile.file' config value is not being specified in the extensions configuration"
62
+ );
63
+ }
64
+ const storage = new FileInstallationStorage.FileInstallationStorage(filePath);
65
+ storage.initialize();
66
+ return new InstallationDataService(extensionsApi, storage);
67
+ } catch (e) {
68
+ let reason;
69
+ if (e instanceof InstallationInitError.InstallationInitError) {
70
+ reason = e.reason;
71
+ } else if (e instanceof ConfigFormatError.ConfigFormatError) {
72
+ reason = InstallationInitError.InstallationInitErrorReason.INVALID_CONFIG;
73
+ } else {
74
+ reason = InstallationInitError.InstallationInitErrorReason.UNKNOWN;
75
+ }
76
+ return serviceWithInitializationError(
77
+ reason,
78
+ e.message,
79
+ reason === InstallationInitError.InstallationInitErrorReason.UNKNOWN ? e : undefined
80
+ );
81
+ }
82
+ }
83
+ async getPluginDynamicArtifacts(plugin) {
84
+ const extensionsPackages = await this.extensionsApi.getPluginPackages(
85
+ plugin.metadata.namespace ?? catalogModel.DEFAULT_NAMESPACE,
86
+ plugin.metadata.name
87
+ );
88
+ return new Set(
89
+ extensionsPackages.flatMap(
90
+ (p) => p.spec?.dynamicArtifact ? [p.spec.dynamicArtifact] : []
91
+ )
92
+ );
93
+ }
94
+ getInitializationError() {
95
+ return this.initializationError;
96
+ }
97
+ getPackageConfig(packageDynamicArtifact) {
98
+ return this.installationStorage.getPackage(packageDynamicArtifact);
99
+ }
100
+ async getPluginConfig(plugin) {
101
+ const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);
102
+ return this.installationStorage.getPackages(dynamicArtifacts);
103
+ }
104
+ updatePackageConfig(packageDynamicArtifact, newConfig) {
105
+ this.installationStorage.updatePackage(packageDynamicArtifact, newConfig);
106
+ }
107
+ async updatePluginConfig(plugin, newConfig) {
108
+ const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);
109
+ this.installationStorage.updatePackages(dynamicArtifacts, newConfig);
110
+ }
111
+ setPackageDisabled(packageDynamicArtifact, disabled) {
112
+ this.installationStorage.setPackageDisabled(
113
+ packageDynamicArtifact,
114
+ disabled
115
+ );
116
+ }
117
+ async setPluginDisabled(plugin, disabled) {
118
+ const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);
119
+ this.installationStorage.setPackagesDisabled(dynamicArtifacts, disabled);
120
+ }
121
+ }
122
+
123
+ exports.InstallationDataService = InstallationDataService;
124
+ //# sourceMappingURL=InstallationDataService.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstallationDataService.cjs.js","sources":["../../src/installation/InstallationDataService.ts"],"sourcesContent":["/*\n * Copyright 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 ExtensionsApi,\n ExtensionsPlugin,\n} from '@red-hat-developer-hub/backstage-plugin-extensions-common';\nimport { DEFAULT_NAMESPACE } from '@backstage/catalog-model';\nimport {\n FileInstallationStorage,\n InstallationStorage,\n} from './FileInstallationStorage';\nimport type { Config } from '@backstage/config';\nimport {\n InstallationInitError,\n InstallationInitErrorReason,\n InstallationInitErrorReasonKeys,\n} from '../errors/InstallationInitError';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ConfigFormatError } from '../errors/ConfigFormatError';\n\nexport class InstallationDataService {\n private constructor(\n private readonly extensionsApi: ExtensionsApi,\n private readonly _installationStorage?: InstallationStorage,\n private readonly initializationError?: InstallationInitError,\n ) {}\n\n private get installationStorage(): InstallationStorage {\n if (!this._installationStorage) {\n throw new Error('Installation storage is not initialized', {\n cause: this.initializationError,\n });\n }\n return this._installationStorage;\n }\n\n static fromConfig(deps: {\n config: Config;\n extensionsApi: ExtensionsApi;\n logger: LoggerService;\n }): InstallationDataService {\n const { config, extensionsApi, logger } = deps;\n\n const serviceWithInitializationError = (\n reason: InstallationInitErrorReasonKeys,\n message: string,\n cause?: Error,\n ): InstallationDataService => {\n if (\n reason ===\n InstallationInitErrorReason.INSTALLATION_DISABLED_IN_PRODUCTION ||\n reason === InstallationInitErrorReason.INSTALLATION_DISABLED\n ) {\n logger.warn(message);\n } else {\n logger.error(\n `Installation feature is disabled. Error while loading data: ${message}`,\n );\n }\n return new InstallationDataService(\n extensionsApi,\n undefined,\n new InstallationInitError(reason, message, cause),\n );\n };\n\n try {\n const node_env = process.env.NODE_ENV ?? 'development';\n if (node_env === 'production') {\n return serviceWithInitializationError(\n InstallationInitErrorReason.INSTALLATION_DISABLED_IN_PRODUCTION,\n 'Installation feature is disabled in production',\n );\n }\n\n const installationEnabled = config.getOptionalBoolean(\n 'extensions.installation.enabled',\n );\n if (!installationEnabled) {\n return serviceWithInitializationError(\n InstallationInitErrorReason.INSTALLATION_DISABLED,\n \"Installation feature is disabled under 'extensions.installation.enabled'\",\n );\n }\n\n const filePath = config.getOptionalString(\n 'extensions.installation.saveToSingleFile.file',\n );\n if (!filePath) {\n return serviceWithInitializationError(\n InstallationInitErrorReason.FILE_CONFIG_VALUE_MISSING,\n \"The 'extensions.installation.saveToSingleFile.file' config value is not being specified in the extensions configuration\",\n );\n }\n\n const storage = new FileInstallationStorage(filePath);\n storage.initialize();\n return new InstallationDataService(extensionsApi, storage);\n } catch (e) {\n let reason: InstallationInitErrorReasonKeys;\n if (e instanceof InstallationInitError) {\n reason = e.reason;\n } else if (e instanceof ConfigFormatError) {\n reason = InstallationInitErrorReason.INVALID_CONFIG;\n } else {\n reason = InstallationInitErrorReason.UNKNOWN;\n }\n return serviceWithInitializationError(\n reason,\n e.message,\n reason === InstallationInitErrorReason.UNKNOWN ? e : undefined,\n );\n }\n }\n\n private async getPluginDynamicArtifacts(\n plugin: ExtensionsPlugin,\n ): Promise<Set<string>> {\n const extensionsPackages = await this.extensionsApi.getPluginPackages(\n plugin.metadata.namespace ?? DEFAULT_NAMESPACE,\n plugin.metadata.name,\n );\n\n return new Set(\n extensionsPackages.flatMap(p =>\n p.spec?.dynamicArtifact ? [p.spec.dynamicArtifact] : [],\n ),\n );\n }\n\n getInitializationError(): InstallationInitError | undefined {\n return this.initializationError;\n }\n\n getPackageConfig(packageDynamicArtifact: string): string | undefined {\n return this.installationStorage.getPackage(packageDynamicArtifact);\n }\n\n async getPluginConfig(plugin: ExtensionsPlugin): Promise<string | undefined> {\n const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);\n return this.installationStorage.getPackages(dynamicArtifacts);\n }\n\n updatePackageConfig(packageDynamicArtifact: string, newConfig: string): void {\n this.installationStorage.updatePackage(packageDynamicArtifact, newConfig);\n }\n\n async updatePluginConfig(\n plugin: ExtensionsPlugin,\n newConfig: string,\n ): Promise<void> {\n const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);\n this.installationStorage.updatePackages(dynamicArtifacts, newConfig);\n }\n\n setPackageDisabled(packageDynamicArtifact: string, disabled: boolean) {\n this.installationStorage.setPackageDisabled(\n packageDynamicArtifact,\n disabled,\n );\n }\n\n async setPluginDisabled(plugin: ExtensionsPlugin, disabled: boolean) {\n const dynamicArtifacts = await this.getPluginDynamicArtifacts(plugin);\n this.installationStorage.setPackagesDisabled(dynamicArtifacts, disabled);\n }\n}\n"],"names":["InstallationInitErrorReason","InstallationInitError","FileInstallationStorage","ConfigFormatError","DEFAULT_NAMESPACE"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
4
+ var zod = require('zod');
5
+ var backstagePluginExtensionsCommon = require('@red-hat-developer-hub/backstage-plugin-extensions-common');
6
+
7
+ const extensionsPermissionResourceRef = pluginPermissionNode.createPermissionResourceRef().with({
8
+ pluginId: "extensions",
9
+ resourceType: backstagePluginExtensionsCommon.RESOURCE_TYPE_EXTENSIONS_PLUGIN
10
+ });
11
+ const hasPluginName = pluginPermissionNode.createPermissionRule({
12
+ name: "HAS_NAME",
13
+ description: "Should allow users to install the plugin with specified name",
14
+ resourceRef: extensionsPermissionResourceRef,
15
+ paramsSchema: zod.z.object({
16
+ pluginNames: zod.z.string().array().optional().describe("List of plugin names or titles to match on")
17
+ }),
18
+ apply: (plugin, { pluginNames }) => {
19
+ return pluginNames && pluginNames.length > 0 ? pluginNames?.some(
20
+ (name) => name.toLowerCase() === plugin.metadata.title?.toLowerCase() || name.toLowerCase() === plugin.metadata.name.toLowerCase()
21
+ ) : true;
22
+ },
23
+ toQuery: ({ pluginNames }) => ({ key: "name", values: pluginNames })
24
+ });
25
+ const hasAnnotation = pluginPermissionNode.createPermissionRule({
26
+ name: "HAS_ANNOTATION",
27
+ description: "Should allow users to install the plugin with specified annotation",
28
+ resourceRef: extensionsPermissionResourceRef,
29
+ paramsSchema: zod.z.object({
30
+ annotation: zod.z.string().describe("Name of the annotation to match on"),
31
+ value: zod.z.string().optional().describe("Value of the annotation to match on")
32
+ }),
33
+ apply: (plugin, params) => !!plugin.metadata.annotations?.hasOwnProperty(params.annotation) && (params.value === undefined ? true : plugin.metadata.annotations?.[params.annotation] === params.value),
34
+ toQuery: ({ annotation, value }) => ({
35
+ key: annotation,
36
+ values: value ? [value] : undefined
37
+ })
38
+ });
39
+ const rules = { hasPluginName, hasAnnotation };
40
+
41
+ exports.extensionsPermissionResourceRef = extensionsPermissionResourceRef;
42
+ exports.rules = rules;
43
+ //# sourceMappingURL=rules.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.cjs.js","sources":["../../src/permissions/rules.ts"],"sourcesContent":["/*\n * Copyright 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 createPermissionResourceRef,\n createPermissionRule,\n} from '@backstage/plugin-permission-node';\nimport { z } from 'zod';\nimport {\n ExtensionsPlugin,\n RESOURCE_TYPE_EXTENSIONS_PLUGIN,\n} from '@red-hat-developer-hub/backstage-plugin-extensions-common';\n\nexport type ExtentionFilter = {\n key: string;\n values: Array<string> | undefined;\n};\n\nexport type ExtentionFilters =\n | { anyOf: ExtentionFilters[] }\n | { allOf: ExtentionFilters[] }\n | { not: ExtentionFilters }\n | ExtentionFilter;\n\nexport const extensionsPermissionResourceRef = createPermissionResourceRef<\n ExtensionsPlugin,\n ExtentionFilter\n>().with({\n pluginId: 'extensions',\n resourceType: RESOURCE_TYPE_EXTENSIONS_PLUGIN,\n});\n\nexport type ExtensionParams = {\n annotation: string;\n value?: string;\n pluginNames?: string[];\n};\n\nconst hasPluginName = createPermissionRule({\n name: 'HAS_NAME',\n description: 'Should allow users to install the plugin with specified name',\n resourceRef: extensionsPermissionResourceRef,\n\n paramsSchema: z.object({\n pluginNames: z\n .string()\n .array()\n .optional()\n .describe('List of plugin names or titles to match on'),\n }),\n apply: (plugin: ExtensionsPlugin, { pluginNames }) => {\n return pluginNames && pluginNames.length > 0\n ? pluginNames?.some(\n name =>\n name.toLowerCase() === plugin.metadata.title?.toLowerCase() ||\n name.toLowerCase() === plugin.metadata.name.toLowerCase(),\n )\n : true;\n },\n toQuery: ({ pluginNames }) => ({ key: 'name', values: pluginNames }),\n});\n\nconst hasAnnotation = createPermissionRule({\n name: 'HAS_ANNOTATION',\n description:\n 'Should allow users to install the plugin with specified annotation',\n resourceRef: extensionsPermissionResourceRef,\n paramsSchema: z.object({\n annotation: z.string().describe('Name of the annotation to match on'),\n value: z\n .string()\n .optional()\n .describe('Value of the annotation to match on'),\n }),\n apply: (plugin: ExtensionsPlugin, params: ExtensionParams) =>\n !!plugin.metadata.annotations?.hasOwnProperty(params.annotation) &&\n (params.value === undefined\n ? true\n : plugin.metadata.annotations?.[params.annotation] === params.value),\n toQuery: ({ annotation, value }) => ({\n key: annotation,\n values: value ? [value] : undefined,\n }),\n});\n\nexport const rules = { hasPluginName, hasAnnotation };\n"],"names":["createPermissionResourceRef","RESOURCE_TYPE_EXTENSIONS_PLUGIN","createPermissionRule","z"],"mappings":";;;;;;AAqCa,MAAA,+BAAA,GAAkCA,gDAG7C,EAAA,CAAE,IAAK,CAAA;AAAA,EACP,QAAU,EAAA,YAAA;AAAA,EACV,YAAc,EAAAC;AAChB,CAAC;AAQD,MAAM,gBAAgBC,yCAAqB,CAAA;AAAA,EACzC,IAAM,EAAA,UAAA;AAAA,EACN,WAAa,EAAA,8DAAA;AAAA,EACb,WAAa,EAAA,+BAAA;AAAA,EAEb,YAAA,EAAcC,MAAE,MAAO,CAAA;AAAA,IACrB,WAAA,EAAaA,MACV,MAAO,EAAA,CACP,OACA,CAAA,QAAA,EACA,CAAA,QAAA,CAAS,4CAA4C;AAAA,GACzD,CAAA;AAAA,EACD,KAAO,EAAA,CAAC,MAA0B,EAAA,EAAE,aAAkB,KAAA;AACpD,IAAA,OAAO,WAAe,IAAA,WAAA,CAAY,MAAS,GAAA,CAAA,GACvC,WAAa,EAAA,IAAA;AAAA,MACX,CACE,IAAA,KAAA,IAAA,CAAK,WAAY,EAAA,KAAM,OAAO,QAAS,CAAA,KAAA,EAAO,WAAY,EAAA,IAC1D,KAAK,WAAY,EAAA,KAAM,MAAO,CAAA,QAAA,CAAS,KAAK,WAAY;AAAA,KAE5D,GAAA,IAAA;AAAA,GACN;AAAA,EACA,OAAA,EAAS,CAAC,EAAE,WAAA,QAAmB,EAAE,GAAA,EAAK,MAAQ,EAAA,MAAA,EAAQ,WAAY,EAAA;AACpE,CAAC,CAAA;AAED,MAAM,gBAAgBD,yCAAqB,CAAA;AAAA,EACzC,IAAM,EAAA,gBAAA;AAAA,EACN,WACE,EAAA,oEAAA;AAAA,EACF,WAAa,EAAA,+BAAA;AAAA,EACb,YAAA,EAAcC,MAAE,MAAO,CAAA;AAAA,IACrB,UAAY,EAAAA,KAAA,CAAE,MAAO,EAAA,CAAE,SAAS,oCAAoC,CAAA;AAAA,IACpE,OAAOA,KACJ,CAAA,MAAA,GACA,QAAS,EAAA,CACT,SAAS,qCAAqC;AAAA,GAClD,CAAA;AAAA,EACD,KAAA,EAAO,CAAC,MAA0B,EAAA,MAAA,KAChC,CAAC,CAAC,MAAA,CAAO,QAAS,CAAA,WAAA,EAAa,cAAe,CAAA,MAAA,CAAO,UAAU,CAC9D,KAAA,MAAA,CAAO,KAAU,KAAA,SAAA,GACd,IACA,GAAA,MAAA,CAAO,SAAS,WAAc,GAAA,MAAA,CAAO,UAAU,CAAA,KAAM,MAAO,CAAA,KAAA,CAAA;AAAA,EAClE,OAAS,EAAA,CAAC,EAAE,UAAA,EAAY,OAAa,MAAA;AAAA,IACnC,GAAK,EAAA,UAAA;AAAA,IACL,MAAQ,EAAA,KAAA,GAAQ,CAAC,KAAK,CAAI,GAAA;AAAA,GAC5B;AACF,CAAC,CAAA;AAEY,MAAA,KAAA,GAAQ,EAAE,aAAA,EAAe,aAAc;;;;;"}
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var catalogClient = require('@backstage/catalog-client');
5
+ var backendDynamicFeatureService = require('@backstage/backend-dynamic-feature-service');
6
+ var backstagePluginExtensionsCommon = require('@red-hat-developer-hub/backstage-plugin-extensions-common');
7
+ var router = require('./router.cjs.js');
8
+ var InstallationDataService = require('./installation/InstallationDataService.cjs.js');
9
+
10
+ const extensionsPlugin = backendPluginApi.createBackendPlugin({
11
+ pluginId: "extensions",
12
+ register(env) {
13
+ env.registerInit({
14
+ deps: {
15
+ auth: backendPluginApi.coreServices.auth,
16
+ config: backendPluginApi.coreServices.rootConfig,
17
+ httpAuth: backendPluginApi.coreServices.httpAuth,
18
+ httpRouter: backendPluginApi.coreServices.httpRouter,
19
+ discovery: backendPluginApi.coreServices.discovery,
20
+ logger: backendPluginApi.coreServices.logger,
21
+ permissions: backendPluginApi.coreServices.permissions,
22
+ pluginProvider: backendDynamicFeatureService.dynamicPluginsServiceRef
23
+ },
24
+ async init({
25
+ pluginProvider,
26
+ auth,
27
+ config,
28
+ httpAuth,
29
+ httpRouter,
30
+ discovery,
31
+ logger,
32
+ permissions
33
+ }) {
34
+ const catalogApi = new catalogClient.CatalogClient({ discoveryApi: discovery });
35
+ const extensionsApi = new backstagePluginExtensionsCommon.ExtensionsCatalogClient({
36
+ auth,
37
+ catalogApi
38
+ });
39
+ const installationDataService = InstallationDataService.InstallationDataService.fromConfig({
40
+ config,
41
+ extensionsApi,
42
+ logger
43
+ });
44
+ httpRouter.use(
45
+ await router.createRouter({
46
+ httpAuth,
47
+ installationDataService,
48
+ extensionsApi,
49
+ permissions,
50
+ pluginProvider,
51
+ logger,
52
+ config
53
+ })
54
+ );
55
+ }
56
+ });
57
+ }
58
+ });
59
+
60
+ exports.extensionsPlugin = extensionsPlugin;
61
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 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 createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { dynamicPluginsServiceRef } from '@backstage/backend-dynamic-feature-service';\n\nimport {\n ExtensionsApi,\n ExtensionsCatalogClient,\n} from '@red-hat-developer-hub/backstage-plugin-extensions-common';\n\nimport { createRouter } from './router';\nimport { InstallationDataService } from './installation/InstallationDataService';\n\n/**\n * Extensions backend plugin\n *\n * @public\n */\nexport const extensionsPlugin = createBackendPlugin({\n pluginId: 'extensions',\n register(env) {\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n permissions: coreServices.permissions,\n pluginProvider: dynamicPluginsServiceRef,\n },\n async init({\n pluginProvider,\n auth,\n config,\n httpAuth,\n httpRouter,\n discovery,\n logger,\n permissions,\n }) {\n const catalogApi = new CatalogClient({ discoveryApi: discovery });\n\n const extensionsApi: ExtensionsApi = new ExtensionsCatalogClient({\n auth,\n catalogApi,\n });\n\n const installationDataService: InstallationDataService =\n InstallationDataService.fromConfig({\n config,\n extensionsApi,\n logger,\n });\n\n httpRouter.use(\n await createRouter({\n httpAuth,\n installationDataService,\n extensionsApi,\n permissions,\n pluginProvider,\n logger,\n config,\n }),\n );\n },\n });\n },\n});\n\n/**\n * @public\n * @deprecated Use extensionsPlugin instead\n */\nexport const markerplacePlugin = extensionsPlugin;\n"],"names":["createBackendPlugin","coreServices","dynamicPluginsServiceRef","CatalogClient","ExtensionsCatalogClient","InstallationDataService","createRouter"],"mappings":";;;;;;;;;AAoCO,MAAM,mBAAmBA,oCAAoB,CAAA;AAAA,EAClD,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAaA,6BAAa,CAAA,WAAA;AAAA,QAC1B,cAAgB,EAAAC;AAAA,OAClB;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,cAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA;AAAA,QACA,SAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAA,MAAM,aAAa,IAAIC,2BAAA,CAAc,EAAE,YAAA,EAAc,WAAW,CAAA;AAEhE,QAAM,MAAA,aAAA,GAA+B,IAAIC,uDAAwB,CAAA;AAAA,UAC/D,IAAA;AAAA,UACA;AAAA,SACD,CAAA;AAED,QAAM,MAAA,uBAAA,GACJC,gDAAwB,UAAW,CAAA;AAAA,UACjC,MAAA;AAAA,UACA,aAAA;AAAA,UACA;AAAA,SACD,CAAA;AAEH,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,QAAA;AAAA,YACA,uBAAA;AAAA,YACA,aAAA;AAAA,YACA,WAAA;AAAA,YACA,cAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -0,0 +1,364 @@
1
+ 'use strict';
2
+
3
+ var express = require('express');
4
+ var Router = require('express-promise-router');
5
+ var errors = require('@backstage/errors');
6
+ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
7
+ var backstagePluginExtensionsCommon = require('@red-hat-developer-hub/backstage-plugin-extensions-common');
8
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
9
+ var createSearchParams = require('./utils/createSearchParams.cjs.js');
10
+ var removeVerboseSpecContent = require('./utils/removeVerboseSpecContent.cjs.js');
11
+ var rules = require('./permissions/rules.cjs.js');
12
+ var permissionUtils = require('./utils/permissionUtils.cjs.js');
13
+ var ConfigFormatError = require('./errors/ConfigFormatError.cjs.js');
14
+ var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
15
+
16
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
17
+
18
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
19
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
20
+
21
+ async function createRouter(options) {
22
+ const {
23
+ httpAuth,
24
+ extensionsApi,
25
+ permissions,
26
+ installationDataService,
27
+ pluginProvider,
28
+ logger,
29
+ config
30
+ } = options;
31
+ const requireInitializedInstallationDataService = (_req, _res, next) => {
32
+ const error = installationDataService.getInitializationError();
33
+ if (error) {
34
+ throw error;
35
+ }
36
+ next();
37
+ };
38
+ const router = Router__default.default();
39
+ const permissionsIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
40
+ resourceType: backstagePluginExtensionsCommon.RESOURCE_TYPE_EXTENSIONS_PLUGIN,
41
+ permissions: backstagePluginExtensionsCommon.extensionsPermissions,
42
+ rules: Object.values(rules.rules)
43
+ });
44
+ router.use(express__default.default.json());
45
+ router.use(permissionsIntegrationRouter);
46
+ const authorizeConditional = async (request, permission) => {
47
+ const credentials = await httpAuth.credentials(request);
48
+ let decision;
49
+ if (!permission) {
50
+ return { result: pluginPermissionCommon.AuthorizeResult.ALLOW };
51
+ }
52
+ if (permission.type === "resource") {
53
+ decision = (await permissions.authorizeConditional([{ permission }], {
54
+ credentials
55
+ }))[0];
56
+ } else {
57
+ decision = (await permissions.authorize([{ permission }], {
58
+ credentials
59
+ }))[0];
60
+ }
61
+ return decision;
62
+ };
63
+ const getAuthorizedPlugin = async (request, permission) => {
64
+ const decision = await authorizeConditional(request, permission);
65
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
66
+ throw new errors.NotAllowedError(
67
+ `Not allowed to ${permission.attributes.action} the configuration of ${request.params.namespace}:${request.params.name}`
68
+ );
69
+ }
70
+ const plugin = await extensionsApi.getPluginByName(
71
+ request.params.namespace,
72
+ request.params.name
73
+ );
74
+ const hasAccess = decision.result === pluginPermissionCommon.AuthorizeResult.ALLOW || decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL && permissionUtils.matches(plugin, decision.conditions);
75
+ if (!hasAccess) {
76
+ throw new errors.NotAllowedError(
77
+ `Not allowed to ${permission.attributes.action} the configuration of ${request.params.namespace}:${request.params.name}`
78
+ );
79
+ }
80
+ return plugin;
81
+ };
82
+ const getAuthorizedPackage = async (request, permission) => {
83
+ const decision = await authorizeConditional(request, permission);
84
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
85
+ throw new errors.NotAllowedError(
86
+ `Not allowed to ${permission.attributes.action} the configuration of ${request.params.namespace}:${request.params.name}`
87
+ );
88
+ }
89
+ const packagePlugins = await extensionsApi.getPackagePlugins(
90
+ request.params.namespace,
91
+ request.params.name
92
+ );
93
+ const hasAccess = decision.result === pluginPermissionCommon.AuthorizeResult.ALLOW || decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL && packagePlugins.some((plugin) => permissionUtils.matches(plugin, decision.conditions));
94
+ if (!hasAccess) {
95
+ throw new errors.NotAllowedError(
96
+ `Not allowed to ${permission.attributes.action} the configuration of ${request.params.namespace}:${request.params.name}`
97
+ );
98
+ }
99
+ return await extensionsApi.getPackageByName(
100
+ request.params.namespace,
101
+ request.params.name
102
+ );
103
+ };
104
+ router.get("/collections", async (req, res) => {
105
+ const request = backstagePluginExtensionsCommon.decodeGetEntitiesRequest(createSearchParams.createSearchParams(req));
106
+ const collections = await extensionsApi.getCollections(request);
107
+ res.json(collections);
108
+ });
109
+ router.get("/collections/facets", async (req, res) => {
110
+ const request = backstagePluginExtensionsCommon.decodeGetEntityFacetsRequest(createSearchParams.createSearchParams(req));
111
+ const facets = await extensionsApi.getCollectionsFacets(request);
112
+ res.json(facets);
113
+ });
114
+ router.get("/collection/:namespace/:name", async (req, res) => {
115
+ const collection = await extensionsApi.getCollectionByName(
116
+ req.params.namespace,
117
+ req.params.name
118
+ );
119
+ res.json(collection);
120
+ });
121
+ router.get("/collection/:namespace/:name/plugins", async (req, res) => {
122
+ const plugins2 = await extensionsApi.getCollectionPlugins(
123
+ req.params.namespace,
124
+ req.params.name
125
+ );
126
+ removeVerboseSpecContent.removeVerboseSpecContent(plugins2);
127
+ res.json(plugins2);
128
+ });
129
+ router.get("/packages", async (req, res) => {
130
+ const request = backstagePluginExtensionsCommon.decodeGetEntitiesRequest(createSearchParams.createSearchParams(req));
131
+ const packages = await extensionsApi.getPackages(request);
132
+ removeVerboseSpecContent.removeVerboseSpecContent(packages.items);
133
+ res.json(packages);
134
+ });
135
+ router.get("/packages/facets", async (req, res) => {
136
+ const request = backstagePluginExtensionsCommon.decodeGetEntityFacetsRequest(createSearchParams.createSearchParams(req));
137
+ const facets = await extensionsApi.getPackagesFacets(request);
138
+ res.json(facets);
139
+ });
140
+ router.get("/package/:namespace/:name", async (req, res) => {
141
+ res.json(
142
+ await extensionsApi.getPackageByName(
143
+ req.params.namespace,
144
+ req.params.name
145
+ )
146
+ );
147
+ });
148
+ router.get(
149
+ "/package/:namespace/:name/configuration",
150
+ requireInitializedInstallationDataService,
151
+ async (req, res) => {
152
+ const extensionsPackage = await getAuthorizedPackage(
153
+ req,
154
+ backstagePluginExtensionsCommon.extensionsPluginReadPermission
155
+ );
156
+ if (!extensionsPackage.spec?.dynamicArtifact) {
157
+ throw new Error(
158
+ `Package catalog entity ${extensionsPackage.metadata.name} is missing 'spec.dynamicArtifact'`
159
+ );
160
+ }
161
+ const result = installationDataService.getPackageConfig(
162
+ extensionsPackage.spec?.dynamicArtifact
163
+ );
164
+ res.status(200).json({ configYaml: result });
165
+ }
166
+ );
167
+ router.post(
168
+ "/package/:namespace/:name/configuration",
169
+ requireInitializedInstallationDataService,
170
+ async (req, res) => {
171
+ const extensionsPackage = await getAuthorizedPackage(
172
+ req,
173
+ backstagePluginExtensionsCommon.extensionsPluginWritePermission
174
+ );
175
+ if (!extensionsPackage.spec?.dynamicArtifact) {
176
+ throw new Error(
177
+ `Package ${extensionsPackage.metadata.name} is missing 'spec.dynamicArtifact'`
178
+ );
179
+ }
180
+ const newConfig = req.body.configYaml;
181
+ if (!newConfig) {
182
+ throw new errors.InputError("'configYaml' object must be present");
183
+ }
184
+ try {
185
+ installationDataService.updatePackageConfig(
186
+ extensionsPackage.spec.dynamicArtifact,
187
+ newConfig
188
+ );
189
+ } catch (e) {
190
+ if (e instanceof ConfigFormatError.ConfigFormatError) {
191
+ throw new errors.InputError(e.message);
192
+ }
193
+ throw e;
194
+ }
195
+ res.status(200).json({ status: "OK" });
196
+ }
197
+ );
198
+ router.get("/environment", async (_req, res) => {
199
+ res.status(200).json({
200
+ nodeEnv: process.env.NODE_ENV || "development"
201
+ });
202
+ });
203
+ router.patch(
204
+ "/package/:namespace/:name/configuration/disable",
205
+ requireInitializedInstallationDataService,
206
+ async (req, res) => {
207
+ const extensionsPackage = await getAuthorizedPackage(
208
+ req,
209
+ backstagePluginExtensionsCommon.extensionsPluginWritePermission
210
+ );
211
+ if (!extensionsPackage.spec?.dynamicArtifact) {
212
+ throw new Error(
213
+ `Package catalog entity ${extensionsPackage.metadata.name} is missing 'spec.dynamicArtifact'`
214
+ );
215
+ }
216
+ const disabled = req.body.disabled;
217
+ if (typeof disabled !== "boolean") {
218
+ throw new errors.InputError("'disabled' must be present boolean");
219
+ }
220
+ installationDataService.setPackageDisabled(
221
+ extensionsPackage.spec.dynamicArtifact,
222
+ disabled
223
+ );
224
+ res.status(200).json({ status: "OK" });
225
+ }
226
+ );
227
+ router.get("/plugins", async (req, res) => {
228
+ const request = backstagePluginExtensionsCommon.decodeGetEntitiesRequest(createSearchParams.createSearchParams(req));
229
+ const plugins2 = await extensionsApi.getPlugins(request);
230
+ removeVerboseSpecContent.removeVerboseSpecContent(plugins2.items);
231
+ res.json(plugins2);
232
+ });
233
+ router.get("/plugins/facets", async (req, res) => {
234
+ const request = backstagePluginExtensionsCommon.decodeGetEntityFacetsRequest(createSearchParams.createSearchParams(req));
235
+ const facets = await extensionsApi.getPluginFacets(request);
236
+ res.json(facets);
237
+ });
238
+ router.get("/plugins/configure", async (_req, res) => {
239
+ const isPluginInstallationEnabled = config.getOptionalBoolean("extensions.installation.enabled") ?? false;
240
+ res.json({ enabled: isPluginInstallationEnabled });
241
+ });
242
+ router.get("/plugin/:namespace/:name", async (req, res) => {
243
+ const plugin = await extensionsApi.getPluginByName(
244
+ req.params.namespace,
245
+ req.params.name
246
+ );
247
+ res.json(plugin);
248
+ });
249
+ router.get(
250
+ "/plugin/:namespace/:name/configuration/authorize",
251
+ async (req, res) => {
252
+ const [readDecision, installDecision] = await Promise.all([
253
+ authorizeConditional(req, backstagePluginExtensionsCommon.extensionsPluginReadPermission),
254
+ authorizeConditional(req, backstagePluginExtensionsCommon.extensionsPluginWritePermission)
255
+ ]);
256
+ if (readDecision.result === pluginPermissionCommon.AuthorizeResult.DENY && installDecision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
257
+ res.status(200).json({ read: "DENY", write: "DENY" });
258
+ return;
259
+ }
260
+ let authorizedActions = {};
261
+ let plugin;
262
+ const evaluateConditional = async (decision, action) => {
263
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
264
+ if (!plugin) {
265
+ plugin = await extensionsApi.getPluginByName(
266
+ req.params.namespace,
267
+ req.params.name
268
+ );
269
+ }
270
+ if (permissionUtils.matches(plugin, decision.conditions)) {
271
+ authorizedActions = { ...authorizedActions, [action]: "ALLOW" };
272
+ }
273
+ } else if (decision.result === pluginPermissionCommon.AuthorizeResult.ALLOW) {
274
+ authorizedActions = { ...authorizedActions, [action]: "ALLOW" };
275
+ }
276
+ };
277
+ await Promise.all([
278
+ evaluateConditional(readDecision, "read"),
279
+ evaluateConditional(installDecision, "write")
280
+ ]);
281
+ if (Object.keys(authorizedActions).length === 0) {
282
+ res.status(200).json({ read: "DENY", write: "DENY" });
283
+ } else {
284
+ res.status(200).json(authorizedActions);
285
+ }
286
+ }
287
+ );
288
+ router.get(
289
+ "/plugin/:namespace/:name/configuration",
290
+ requireInitializedInstallationDataService,
291
+ async (req, res) => {
292
+ const plugin = await getAuthorizedPlugin(
293
+ req,
294
+ backstagePluginExtensionsCommon.extensionsPluginReadPermission
295
+ );
296
+ const result = await installationDataService.getPluginConfig(plugin);
297
+ res.status(200).json({ configYaml: result });
298
+ }
299
+ );
300
+ router.post(
301
+ "/plugin/:namespace/:name/configuration",
302
+ requireInitializedInstallationDataService,
303
+ async (req, res) => {
304
+ const plugin = await getAuthorizedPlugin(
305
+ req,
306
+ backstagePluginExtensionsCommon.extensionsPluginWritePermission
307
+ );
308
+ const newConfig = req.body.configYaml;
309
+ if (!newConfig) {
310
+ throw new errors.InputError("'configYaml' object must be present");
311
+ }
312
+ try {
313
+ await installationDataService.updatePluginConfig(plugin, newConfig);
314
+ } catch (e) {
315
+ if (e instanceof ConfigFormatError.ConfigFormatError) {
316
+ throw new errors.InputError(e.message);
317
+ }
318
+ throw e;
319
+ }
320
+ res.status(200).json({ status: "OK" });
321
+ }
322
+ );
323
+ router.patch(
324
+ "/plugin/:namespace/:name/configuration/disable",
325
+ requireInitializedInstallationDataService,
326
+ async (req, res) => {
327
+ const plugin = await getAuthorizedPlugin(
328
+ req,
329
+ backstagePluginExtensionsCommon.extensionsPluginWritePermission
330
+ );
331
+ const disabled = req.body.disabled;
332
+ if (typeof disabled !== "boolean") {
333
+ throw new errors.InputError("'disabled' must be present boolean");
334
+ }
335
+ await installationDataService.setPluginDisabled(plugin, disabled);
336
+ res.status(200).json({ status: "OK" });
337
+ }
338
+ );
339
+ router.get("/plugin/:namespace/:name/packages", async (req, res) => {
340
+ const packages = await extensionsApi.getPluginPackages(
341
+ req.params.namespace,
342
+ req.params.name
343
+ );
344
+ res.json(packages);
345
+ });
346
+ const plugins = pluginProvider.plugins();
347
+ const dynamicPlugins = plugins.map((p) => {
348
+ if (p.platform === "node") {
349
+ const { installer, ...rest } = p;
350
+ return rest;
351
+ }
352
+ return p;
353
+ });
354
+ router.get("/loaded-plugins", async (req, response) => {
355
+ await httpAuth.credentials(req, { allow: ["user", "service"] });
356
+ response.send(dynamicPlugins);
357
+ });
358
+ const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config });
359
+ router.use(middleware.error());
360
+ return router;
361
+ }
362
+
363
+ exports.createRouter = createRouter;
364
+ //# sourceMappingURL=router.cjs.js.map