@backstage-community/plugin-rbac-backend 6.2.6 → 7.0.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +44 -6
  3. package/dist/auditor/auditor.cjs.js +5 -0
  4. package/dist/auditor/auditor.cjs.js.map +1 -1
  5. package/dist/auditor/rest-interceptor.cjs.js +5 -0
  6. package/dist/auditor/rest-interceptor.cjs.js.map +1 -1
  7. package/dist/database/extra-permission-enabled-plugins-storage.cjs.js +21 -0
  8. package/dist/database/extra-permission-enabled-plugins-storage.cjs.js.map +1 -0
  9. package/dist/index.d.ts +7 -6
  10. package/dist/permissions/conditions.cjs.js +6 -5
  11. package/dist/permissions/conditions.cjs.js.map +1 -1
  12. package/dist/permissions/resource.cjs.js +12 -0
  13. package/dist/permissions/resource.cjs.js.map +1 -0
  14. package/dist/permissions/rules.cjs.js +3 -4
  15. package/dist/permissions/rules.cjs.js.map +1 -1
  16. package/dist/plugin.cjs.js +12 -6
  17. package/dist/plugin.cjs.js.map +1 -1
  18. package/dist/service/extendable-id-provider.cjs.js +32 -0
  19. package/dist/service/extendable-id-provider.cjs.js.map +1 -0
  20. package/dist/service/permission-definition-routes.cjs.js +104 -0
  21. package/dist/service/permission-definition-routes.cjs.js.map +1 -0
  22. package/dist/service/plugin-endpoints.cjs.js +5 -4
  23. package/dist/service/plugin-endpoints.cjs.js.map +1 -1
  24. package/dist/service/policies-rest-api.cjs.js +153 -147
  25. package/dist/service/policies-rest-api.cjs.js.map +1 -1
  26. package/dist/service/policy-builder.cjs.js +43 -29
  27. package/dist/service/policy-builder.cjs.js.map +1 -1
  28. package/dist/service/router.cjs.js +3 -1
  29. package/dist/service/router.cjs.js.map +1 -1
  30. package/dist/validation/plugin-validation.cjs.js +15 -0
  31. package/dist/validation/plugin-validation.cjs.js.map +1 -0
  32. package/migrations/20250509110032_migrations.js +29 -0
  33. package/package.json +18 -19
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-definition-routes.cjs.js","sources":["../../src/service/permission-definition-routes.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 */\nimport {\n PermissionDependentPluginList,\n policyEntityCreatePermission,\n policyEntityDeletePermission,\n policyEntityReadPermission,\n} from '@backstage-community/plugin-rbac-common';\nimport { logAuditorEvent } from '../auditor/rest-interceptor';\nimport { PluginPermissionMetadataCollector } from './plugin-endpoints';\nimport {\n PermissionDependentPluginDTO,\n PermissionDependentPluginStore,\n} from '../database/extra-permission-enabled-plugins-storage';\nimport { authorizeConditional } from './policies-rest-api';\nimport {\n AuditorService,\n AuthService,\n HttpAuthService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport { ExtendablePluginIdProvider } from './extendable-id-provider';\nimport {\n ConflictError,\n NotAllowedError,\n NotFoundError,\n} from '@backstage/errors';\nimport { validatePermissionDependentPlugin } from '../validation/plugin-validation';\nimport express from 'express';\n\nexport function registerPermissionDefinitionRoutes(\n router: express.Router,\n pluginPermMetaData: PluginPermissionMetadataCollector,\n pluginIdProvider: ExtendablePluginIdProvider,\n extraPluginsIdStorage: PermissionDependentPluginStore,\n deps: {\n auth: AuthService;\n httpAuth: HttpAuthService;\n auditor: AuditorService;\n permissions: PermissionsService;\n },\n) {\n const { auth, auditor } = deps;\n\n router.get(\n '/plugins/policies',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const body = await pluginPermMetaData.getPluginPolicies(auth);\n\n response.json(body);\n },\n );\n\n router.get(\n '/plugins/condition-rules',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const body = await pluginPermMetaData.getPluginConditionRules(auth);\n\n response.json(body);\n },\n );\n\n router.get(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityReadPermission, deps);\n\n const actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n\n router.post(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityCreatePermission, deps);\n const pluginIds: PermissionDependentPluginList = request.body;\n validatePermissionDependentPlugin(pluginIds);\n const pluginDtos = permissionDependentPluginListToDTO(pluginIds);\n\n let actualPluginIds = await pluginIdProvider.getPluginIds();\n const conflictedIds = pluginIds.ids.filter(id =>\n actualPluginIds.includes(id),\n );\n if (conflictedIds.length > 0) {\n throw new ConflictError(\n `Plugin IDs ${JSON.stringify(conflictedIds)} already exist in the system. Please use a different set of plugin ids.`,\n );\n }\n await extraPluginsIdStorage.addPlugins(pluginDtos);\n response.locals.meta = pluginIds;\n\n actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n\n router.delete(\n '/plugins/id',\n logAuditorEvent(auditor),\n async (request, response) => {\n await authorizeConditional(request, policyEntityDeletePermission, deps);\n const pluginIds: PermissionDependentPluginList = request.body;\n validatePermissionDependentPlugin(pluginIds);\n const configuredPluginIds = pluginIds.ids.filter(pluginId =>\n pluginIdProvider.isConfiguredPluginId(pluginId),\n );\n if (configuredPluginIds.length > 0) {\n throw new NotAllowedError(\n `Plugin IDs ${JSON.stringify(pluginIds.ids)} can be removed only with help of configuration.`,\n );\n }\n\n let actualPluginIds = await pluginIdProvider.getPluginIds();\n const notFoundPlugins = pluginIds.ids.filter(\n pluginToDel => !actualPluginIds.includes(pluginToDel),\n );\n if (notFoundPlugins.length > 0) {\n throw new NotFoundError(\n `Plugin IDs ${JSON.stringify(notFoundPlugins)} were not found.`,\n );\n }\n\n await extraPluginsIdStorage.deletePlugins(pluginIds.ids);\n response.locals.meta = pluginIds;\n\n actualPluginIds = await pluginIdProvider.getPluginIds();\n response.status(200).json(pluginIdsToResponse(actualPluginIds));\n },\n );\n}\n\nexport function pluginIdsToResponse(\n pluginIds: string[],\n): PermissionDependentPluginList {\n return { ids: pluginIds };\n}\n\nexport function permissionDependentPluginListToDTO(\n pluginList: PermissionDependentPluginList,\n): PermissionDependentPluginDTO[] {\n return pluginList.ids.map(pluginId => {\n return { pluginId };\n });\n}\n"],"names":["logAuditorEvent","authorizeConditional","policyEntityReadPermission","policyEntityCreatePermission","validatePermissionDependentPlugin","ConflictError","policyEntityDeletePermission","NotAllowedError","NotFoundError"],"mappings":"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"}
@@ -12,7 +12,7 @@ const rbacPermissionMetadata = {
12
12
  rules: [rules.rbacRules]
13
13
  };
14
14
  class PluginPermissionMetadataCollector {
15
- pluginIds;
15
+ pluginIdProvider;
16
16
  discovery;
17
17
  logger;
18
18
  urlReader;
@@ -20,9 +20,9 @@ class PluginPermissionMetadataCollector {
20
20
  deps,
21
21
  optional
22
22
  }) {
23
- const { discovery, pluginIdProvider, logger, config } = deps;
24
- this.pluginIds = pluginIdProvider.getPluginIds();
23
+ const { discovery, logger, config, pluginIdProvider } = deps;
25
24
  this.discovery = discovery;
25
+ this.pluginIdProvider = pluginIdProvider;
26
26
  this.logger = logger;
27
27
  this.urlReader = optional?.urlReader ?? urlReader.UrlReaders.default({
28
28
  config,
@@ -55,7 +55,8 @@ class PluginPermissionMetadataCollector {
55
55
  };
56
56
  async getPluginMetaData(auth) {
57
57
  let pluginResponses = [];
58
- for (const pluginId of this.pluginIds) {
58
+ const pluginIds = await this.pluginIdProvider.getPluginIds();
59
+ for (const pluginId of pluginIds) {
59
60
  try {
60
61
  const { token } = await auth.getPluginRequestToken({
61
62
  onBehalfOf: await auth.getOwnServiceCredentials(),
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-endpoints.cjs.js","sources":["../../src/service/plugin-endpoints.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 */\nimport {\n FetchUrlReader,\n ReaderFactory,\n UrlReaders,\n} from '@backstage/backend-defaults/urlReader';\nimport type {\n AuthService,\n DiscoveryService,\n LoggerService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { isError } from '@backstage/errors';\nimport {\n isResourcePermission,\n Permission,\n} from '@backstage/plugin-permission-common';\nimport type {\n MetadataResponse,\n MetadataResponseSerializedRule,\n} from '@backstage/plugin-permission-node';\n\nimport {\n policyEntityPermissions,\n type PluginPermissionMetaData,\n type PolicyDetails,\n} from '@backstage-community/plugin-rbac-common';\nimport type { PluginIdProvider } from '@backstage-community/plugin-rbac-node';\nimport { rbacRules } from '../permissions';\n\ntype PluginMetadataResponse = {\n pluginId: string;\n metaDataResponse: MetadataResponse;\n};\n\nexport type PluginMetadataResponseSerializedRule = {\n pluginId: string;\n rules: MetadataResponseSerializedRule[];\n};\n\nconst rbacPermissionMetadata: MetadataResponse = {\n permissions: policyEntityPermissions,\n rules: [rbacRules],\n};\n\nexport class PluginPermissionMetadataCollector {\n private readonly pluginIds: string[];\n private readonly discovery: DiscoveryService;\n private readonly logger: LoggerService;\n private readonly urlReader: UrlReaderService;\n\n constructor({\n deps,\n optional,\n }: {\n deps: {\n discovery: DiscoveryService;\n pluginIdProvider: PluginIdProvider;\n logger: LoggerService;\n config: Config;\n };\n optional?: {\n urlReader?: UrlReaderService;\n };\n }) {\n const { discovery, pluginIdProvider, logger, config } = deps;\n this.pluginIds = pluginIdProvider.getPluginIds();\n this.discovery = discovery;\n this.logger = logger;\n this.urlReader =\n optional?.urlReader ??\n UrlReaders.default({\n config,\n logger,\n factories: [PluginPermissionMetadataCollector.permissionFactory],\n });\n }\n\n async getPluginConditionRules(\n auth: AuthService,\n ): Promise<PluginMetadataResponseSerializedRule[]> {\n const pluginMetadata = await this.getPluginMetaData(auth);\n\n return pluginMetadata\n .filter(metadata => metadata.metaDataResponse.rules.length > 0)\n .map(metadata => {\n return {\n pluginId: metadata.pluginId,\n rules: metadata.metaDataResponse.rules,\n };\n });\n }\n\n async getPluginPolicies(\n auth: AuthService,\n ): Promise<PluginPermissionMetaData[]> {\n const pluginMetadata = await this.getPluginMetaData(auth);\n\n return pluginMetadata\n .filter(metadata => metadata.metaDataResponse.permissions !== undefined)\n .map(metadata => {\n return {\n pluginId: metadata.pluginId,\n policies: permissionsToCasbinPolicies(\n metadata.metaDataResponse.permissions!,\n ),\n };\n });\n }\n\n private static permissionFactory: ReaderFactory = () => {\n return [{ reader: new FetchUrlReader(), predicate: (_url: URL) => true }];\n };\n\n private async getPluginMetaData(\n auth: AuthService,\n ): Promise<PluginMetadataResponse[]> {\n let pluginResponses: PluginMetadataResponse[] = [];\n\n for (const pluginId of this.pluginIds) {\n try {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: pluginId,\n });\n\n const permMetaData = await this.getMetadataByPluginId(pluginId, token);\n if (permMetaData) {\n pluginResponses = [\n ...pluginResponses,\n {\n metaDataResponse: permMetaData,\n pluginId,\n },\n ];\n }\n } catch (error) {\n this.logger.error(\n `Failed to retrieve permission metadata for ${pluginId}. ${error}`,\n );\n }\n }\n\n return pluginResponses;\n }\n\n async getMetadataByPluginId(\n pluginId: string,\n token: string | undefined,\n ): Promise<MetadataResponse | undefined> {\n let permMetaData: MetadataResponse | undefined;\n\n // Work around: This is needed for start up whenever a conditional policy for the plugin permission in the yaml file\n // will make a check to the well known endpoint\n // However, our plugin has not completely started and as such will throw a 503 error\n // TODO: see if we are able to remove this after we migrate to the permission registry\n if (pluginId === 'permission') {\n return rbacPermissionMetadata;\n }\n\n try {\n const baseEndpoint = await this.discovery.getBaseUrl(pluginId);\n const wellKnownURL = `${baseEndpoint}/.well-known/backstage/permissions/metadata`;\n\n const permResp = await this.urlReader.readUrl(wellKnownURL, { token });\n const permMetaDataRaw = (await permResp.buffer()).toString();\n\n try {\n permMetaData = JSON.parse(permMetaDataRaw);\n } catch (err) {\n // workaround for https://issues.redhat.com/browse/RHIDP-1456\n return undefined;\n }\n } catch (err) {\n if (isError(err) && err.name === 'NotFoundError') {\n this.logger.warn(\n `No permission metadata found for ${pluginId}. ${err}`,\n );\n return undefined;\n }\n this.logger.error(\n `Failed to retrieve permission metadata for ${pluginId}. ${err}`,\n );\n }\n return permMetaData;\n }\n}\n\nfunction permissionsToCasbinPolicies(\n permissions: Permission[],\n): PolicyDetails[] {\n const policies: PolicyDetails[] = [];\n for (const permission of permissions) {\n if (isResourcePermission(permission)) {\n policies.push({\n resourceType: permission.resourceType,\n name: permission.name,\n policy: permission.attributes.action || 'use',\n });\n } else {\n policies.push({\n name: permission.name,\n policy: permission.attributes.action || 'use',\n });\n }\n }\n\n return policies;\n}\n"],"names":["policyEntityPermissions","rbacRules","UrlReaders","FetchUrlReader","isError","isResourcePermission"],"mappings":"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"}
1
+ {"version":3,"file":"plugin-endpoints.cjs.js","sources":["../../src/service/plugin-endpoints.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 */\nimport {\n FetchUrlReader,\n ReaderFactory,\n UrlReaders,\n} from '@backstage/backend-defaults/urlReader';\nimport type {\n AuthService,\n DiscoveryService,\n LoggerService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { isError } from '@backstage/errors';\nimport {\n isResourcePermission,\n Permission,\n} from '@backstage/plugin-permission-common';\nimport type {\n MetadataResponse,\n MetadataResponseSerializedRule,\n} from '@backstage/plugin-permission-node';\n\nimport {\n policyEntityPermissions,\n type PluginPermissionMetaData,\n type PolicyDetails,\n} from '@backstage-community/plugin-rbac-common';\nimport { rbacRules } from '../permissions';\nimport { ExtendablePluginIdProvider } from './extendable-id-provider';\n\ntype PluginMetadataResponse = {\n pluginId: string;\n metaDataResponse: MetadataResponse;\n};\n\nexport type PluginMetadataResponseSerializedRule = {\n pluginId: string;\n rules: MetadataResponseSerializedRule[];\n};\n\nconst rbacPermissionMetadata: MetadataResponse = {\n permissions: policyEntityPermissions,\n rules: [rbacRules],\n};\n\nexport class PluginPermissionMetadataCollector {\n private readonly pluginIdProvider: ExtendablePluginIdProvider;\n private readonly discovery: DiscoveryService;\n private readonly logger: LoggerService;\n private readonly urlReader: UrlReaderService;\n\n constructor({\n deps,\n optional,\n }: {\n deps: {\n discovery: DiscoveryService;\n pluginIdProvider: ExtendablePluginIdProvider;\n logger: LoggerService;\n config: Config;\n };\n optional?: {\n urlReader?: UrlReaderService;\n };\n }) {\n const { discovery, logger, config, pluginIdProvider } = deps;\n this.discovery = discovery;\n this.pluginIdProvider = pluginIdProvider;\n this.logger = logger;\n this.urlReader =\n optional?.urlReader ??\n UrlReaders.default({\n config,\n logger,\n factories: [PluginPermissionMetadataCollector.permissionFactory],\n });\n }\n\n async getPluginConditionRules(\n auth: AuthService,\n ): Promise<PluginMetadataResponseSerializedRule[]> {\n const pluginMetadata = await this.getPluginMetaData(auth);\n\n return pluginMetadata\n .filter(metadata => metadata.metaDataResponse.rules.length > 0)\n .map(metadata => {\n return {\n pluginId: metadata.pluginId,\n rules: metadata.metaDataResponse.rules,\n };\n });\n }\n\n async getPluginPolicies(\n auth: AuthService,\n ): Promise<PluginPermissionMetaData[]> {\n const pluginMetadata = await this.getPluginMetaData(auth);\n\n return pluginMetadata\n .filter(metadata => metadata.metaDataResponse.permissions !== undefined)\n .map(metadata => {\n return {\n pluginId: metadata.pluginId,\n policies: permissionsToCasbinPolicies(\n metadata.metaDataResponse.permissions!,\n ),\n };\n });\n }\n\n private static permissionFactory: ReaderFactory = () => {\n return [{ reader: new FetchUrlReader(), predicate: (_url: URL) => true }];\n };\n\n private async getPluginMetaData(\n auth: AuthService,\n ): Promise<PluginMetadataResponse[]> {\n let pluginResponses: PluginMetadataResponse[] = [];\n\n const pluginIds = await this.pluginIdProvider.getPluginIds();\n for (const pluginId of pluginIds) {\n try {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: pluginId,\n });\n\n const permMetaData = await this.getMetadataByPluginId(pluginId, token);\n if (permMetaData) {\n pluginResponses = [\n ...pluginResponses,\n {\n metaDataResponse: permMetaData,\n pluginId,\n },\n ];\n }\n } catch (error) {\n this.logger.error(\n `Failed to retrieve permission metadata for ${pluginId}. ${error}`,\n );\n }\n }\n\n return pluginResponses;\n }\n\n async getMetadataByPluginId(\n pluginId: string,\n token: string | undefined,\n ): Promise<MetadataResponse | undefined> {\n let permMetaData: MetadataResponse | undefined;\n\n // Work around: This is needed for start up whenever a conditional policy for the plugin permission in the yaml file\n // will make a check to the well known endpoint\n // However, our plugin has not completely started and as such will throw a 503 error\n // TODO: see if we are able to remove this after we migrate to the permission registry\n if (pluginId === 'permission') {\n return rbacPermissionMetadata;\n }\n\n try {\n const baseEndpoint = await this.discovery.getBaseUrl(pluginId);\n const wellKnownURL = `${baseEndpoint}/.well-known/backstage/permissions/metadata`;\n\n const permResp = await this.urlReader.readUrl(wellKnownURL, { token });\n const permMetaDataRaw = (await permResp.buffer()).toString();\n\n try {\n permMetaData = JSON.parse(permMetaDataRaw);\n } catch (err) {\n // workaround for https://issues.redhat.com/browse/RHIDP-1456\n return undefined;\n }\n } catch (err) {\n if (isError(err) && err.name === 'NotFoundError') {\n this.logger.warn(\n `No permission metadata found for ${pluginId}. ${err}`,\n );\n return undefined;\n }\n this.logger.error(\n `Failed to retrieve permission metadata for ${pluginId}. ${err}`,\n );\n }\n return permMetaData;\n }\n}\n\nfunction permissionsToCasbinPolicies(\n permissions: Permission[],\n): PolicyDetails[] {\n const policies: PolicyDetails[] = [];\n for (const permission of permissions) {\n if (isResourcePermission(permission)) {\n policies.push({\n resourceType: permission.resourceType,\n name: permission.name,\n policy: permission.attributes.action || 'use',\n });\n } else {\n policies.push({\n name: permission.name,\n policy: permission.attributes.action || 'use',\n });\n }\n }\n\n return policies;\n}\n"],"names":["policyEntityPermissions","rbacRules","UrlReaders","FetchUrlReader","isError","isResourcePermission"],"mappings":"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"}
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
- var pluginPermissionBackend = require('@backstage/plugin-permission-backend');
5
4
  var pluginPermissionCommon = require('@backstage/plugin-permission-common');
6
- var pluginPermissionNode = require('@backstage/plugin-permission-node');
7
5
  var lodash = require('lodash');
8
6
  var pluginRbacCommon = require('@backstage-community/plugin-rbac-common');
9
7
  var restInterceptor = require('../auditor/rest-interceptor.cjs.js');
@@ -12,76 +10,74 @@ var helper = require('../helper.cjs.js');
12
10
  var conditionValidation = require('../validation/condition-validation.cjs.js');
13
11
  var policiesValidation = require('../validation/policies-validation.cjs.js');
14
12
  var conditions = require('../permissions/conditions.cjs.js');
15
- var rules = require('../permissions/rules.cjs.js');
13
+ require('../permissions/rules.cjs.js');
14
+ var permissionDefinitionRoutes = require('./permission-definition-routes.cjs.js');
15
+ var router = require('./router.cjs.js');
16
16
 
17
+ async function authorizeConditional(request, permission, deps) {
18
+ const { auth, httpAuth, permissions } = deps;
19
+ const credentials = await httpAuth.credentials(request, {
20
+ allow: ["user", "service"]
21
+ });
22
+ if (auth.isPrincipal(credentials, "service") && permission !== pluginRbacCommon.policyEntityReadPermission) {
23
+ throw new errors.NotAllowedError(
24
+ `Only credential principal with type 'user' permitted to modify permissions`
25
+ );
26
+ }
27
+ let decision;
28
+ if (permission.type === "resource") {
29
+ decision = (await permissions.authorizeConditional([{ permission }], {
30
+ credentials
31
+ }))[0];
32
+ } else {
33
+ decision = (await permissions.authorize([{ permission }], {
34
+ credentials
35
+ }))[0];
36
+ }
37
+ if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
38
+ throw new errors.NotAllowedError();
39
+ }
40
+ return { decision, credentials };
41
+ }
17
42
  class PoliciesServer {
18
- constructor(permissions, options, enforcer, conditionalStorage, pluginPermMetaData, roleMetadata, auditor, rbacProviders) {
19
- this.permissions = permissions;
43
+ constructor(options, enforcer, conditionalStorage, pluginPermMetaData, roleMetadata, extraPluginsIdStorage, pluginIdProvider, rbacProviders) {
20
44
  this.options = options;
21
45
  this.enforcer = enforcer;
22
46
  this.conditionalStorage = conditionalStorage;
23
47
  this.pluginPermMetaData = pluginPermMetaData;
24
48
  this.roleMetadata = roleMetadata;
25
- this.auditor = auditor;
49
+ this.extraPluginsIdStorage = extraPluginsIdStorage;
50
+ this.pluginIdProvider = pluginIdProvider;
26
51
  this.rbacProviders = rbacProviders;
27
52
  }
28
- async authorizeConditional(request, permission) {
29
- const credentials = await this.options.httpAuth.credentials(request, {
30
- allow: ["user", "service"]
31
- });
32
- if (this.options.auth.isPrincipal(credentials, "service") && permission !== pluginRbacCommon.policyEntityReadPermission) {
33
- throw new errors.NotAllowedError(
34
- `Only credential principal with type 'user' permitted to modify permissions`
35
- );
36
- }
37
- let decision;
38
- if (permission.type === "resource") {
39
- decision = (await this.permissions.authorizeConditional([{ permission }], {
40
- credentials
41
- }))[0];
42
- } else {
43
- decision = (await this.permissions.authorize([{ permission }], {
44
- credentials
45
- }))[0];
46
- }
47
- if (decision.result === pluginPermissionCommon.AuthorizeResult.DENY) {
48
- throw new errors.NotAllowedError();
49
- }
50
- return { decision, credentials };
51
- }
52
53
  async serve() {
53
- const router = await pluginPermissionBackend.createRouter(this.options);
54
- const { logger } = this.options;
55
- const policyPermissionsIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
56
- resourceType: pluginRbacCommon.RESOURCE_TYPE_POLICY_ENTITY,
57
- getResources: (resourceRefs) => Promise.all(
58
- resourceRefs.map((ref) => {
59
- return this.roleMetadata.findRoleMetadata(ref);
60
- })
61
- ),
62
- permissions: pluginRbacCommon.policyEntityPermissions,
63
- rules: Object.values(rules.rules)
64
- });
65
- router.use(policyPermissionsIntegrationRouter);
54
+ const router$1 = await router.createRouter(this.options);
55
+ const { logger, auditor, auth, permissionsRegistry } = this.options;
66
56
  const isPluginEnabled = this.options.config.getOptionalBoolean("permission.enabled");
67
57
  if (!isPluginEnabled) {
68
- return router;
58
+ return router$1;
69
59
  }
70
- router.get("/", async (request, response) => {
71
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission);
60
+ const transformConditions = conditions.conditionTransformerFunc(permissionsRegistry);
61
+ router$1.get("/", async (request, response) => {
62
+ await authorizeConditional(
63
+ request,
64
+ pluginRbacCommon.policyEntityReadPermission,
65
+ this.options
66
+ );
72
67
  response.send({ status: "Authorized" });
73
68
  });
74
- router.get(
69
+ router$1.get(
75
70
  "/policies",
76
- restInterceptor.logAuditorEvent(this.auditor),
71
+ restInterceptor.logAuditorEvent(auditor),
77
72
  async (request, response) => {
78
73
  let conditionsFilter;
79
- const { decision } = await this.authorizeConditional(
74
+ const { decision } = await authorizeConditional(
80
75
  request,
81
- pluginRbacCommon.policyEntityReadPermission
76
+ pluginRbacCommon.policyEntityReadPermission,
77
+ this.options
82
78
  );
83
79
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
84
- conditionsFilter = conditions.transformConditions(decision.conditions);
80
+ conditionsFilter = transformConditions(decision.conditions);
85
81
  }
86
82
  const roleMetadata = await this.roleMetadata.filterForOwnerRoleMetadata(conditionsFilter);
87
83
  let policies = [];
@@ -117,17 +113,18 @@ class PoliciesServer {
117
113
  response.json(body);
118
114
  }
119
115
  );
120
- router.get(
116
+ router$1.get(
121
117
  "/policies/:kind/:namespace/:name",
122
- restInterceptor.logAuditorEvent(this.auditor),
118
+ restInterceptor.logAuditorEvent(auditor),
123
119
  async (request, response) => {
124
120
  let conditionsFilter;
125
- const { decision } = await this.authorizeConditional(
121
+ const { decision } = await authorizeConditional(
126
122
  request,
127
- pluginRbacCommon.policyEntityReadPermission
123
+ pluginRbacCommon.policyEntityReadPermission,
124
+ this.options
128
125
  );
129
126
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
130
- conditionsFilter = conditions.transformConditions(decision.conditions);
127
+ conditionsFilter = transformConditions(decision.conditions);
131
128
  }
132
129
  const roleMetadata = await this.roleMetadata.filterForOwnerRoleMetadata(conditionsFilter);
133
130
  const matchedRoleName = roleMetadata.flatMap((role) => {
@@ -151,17 +148,18 @@ class PoliciesServer {
151
148
  }
152
149
  }
153
150
  );
154
- router.delete(
151
+ router$1.delete(
155
152
  "/policies/:kind/:namespace/:name",
156
- restInterceptor.logAuditorEvent(this.auditor),
153
+ restInterceptor.logAuditorEvent(auditor),
157
154
  async (request, response) => {
158
155
  let conditionsFilter;
159
- const { decision } = await this.authorizeConditional(
156
+ const { decision } = await authorizeConditional(
160
157
  request,
161
- pluginRbacCommon.policyEntityDeletePermission
158
+ pluginRbacCommon.policyEntityDeletePermission,
159
+ this.options
162
160
  );
163
161
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
164
- conditionsFilter = conditions.transformConditions(decision.conditions);
162
+ conditionsFilter = transformConditions(decision.conditions);
165
163
  }
166
164
  const entityRef = this.getEntityReference(request);
167
165
  const policyRaw = request.body;
@@ -182,11 +180,15 @@ class PoliciesServer {
182
180
  response.status(204).end();
183
181
  }
184
182
  );
185
- router.post(
183
+ router$1.post(
186
184
  "/policies",
187
- restInterceptor.logAuditorEvent(this.auditor),
185
+ restInterceptor.logAuditorEvent(auditor),
188
186
  async (request, response) => {
189
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityCreatePermission);
187
+ await authorizeConditional(
188
+ request,
189
+ pluginRbacCommon.policyEntityCreatePermission,
190
+ this.options
191
+ );
190
192
  const policyRaw = request.body;
191
193
  if (lodash.isEmpty(policyRaw)) {
192
194
  throw new errors.InputError(`permission policy must be present`);
@@ -206,17 +208,18 @@ class PoliciesServer {
206
208
  response.status(201).end();
207
209
  }
208
210
  );
209
- router.put(
211
+ router$1.put(
210
212
  "/policies/:kind/:namespace/:name",
211
- restInterceptor.logAuditorEvent(this.auditor),
213
+ restInterceptor.logAuditorEvent(auditor),
212
214
  async (request, response) => {
213
215
  let conditionsFilter;
214
- const { decision } = await this.authorizeConditional(
216
+ const { decision } = await authorizeConditional(
215
217
  request,
216
- pluginRbacCommon.policyEntityUpdatePermission
218
+ pluginRbacCommon.policyEntityUpdatePermission,
219
+ this.options
217
220
  );
218
221
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
219
- conditionsFilter = conditions.transformConditions(decision.conditions);
222
+ conditionsFilter = transformConditions(decision.conditions);
220
223
  }
221
224
  const entityRef = this.getEntityReference(request);
222
225
  const oldPolicyRaw = request.body.oldPolicy;
@@ -267,34 +270,36 @@ class PoliciesServer {
267
270
  response.status(200).end();
268
271
  }
269
272
  );
270
- router.get(
273
+ router$1.get(
271
274
  "/roles",
272
- restInterceptor.logAuditorEvent(this.auditor),
275
+ restInterceptor.logAuditorEvent(auditor),
273
276
  async (request, response) => {
274
277
  let conditionsFilter;
275
- const { decision } = await this.authorizeConditional(
278
+ const { decision } = await authorizeConditional(
276
279
  request,
277
- pluginRbacCommon.policyEntityReadPermission
280
+ pluginRbacCommon.policyEntityReadPermission,
281
+ this.options
278
282
  );
279
283
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
280
- conditionsFilter = conditions.transformConditions(decision.conditions);
284
+ conditionsFilter = transformConditions(decision.conditions);
281
285
  }
282
286
  const roles = await this.enforcer.getGroupingPolicy();
283
287
  const body = await this.transformRoleArray(conditionsFilter, ...roles);
284
288
  response.json(body);
285
289
  }
286
290
  );
287
- router.get(
291
+ router$1.get(
288
292
  "/roles/:kind/:namespace/:name",
289
- restInterceptor.logAuditorEvent(this.auditor),
293
+ restInterceptor.logAuditorEvent(auditor),
290
294
  async (request, response) => {
291
295
  let conditionsFilter;
292
- const { decision } = await this.authorizeConditional(
296
+ const { decision } = await authorizeConditional(
293
297
  request,
294
- pluginRbacCommon.policyEntityReadPermission
298
+ pluginRbacCommon.policyEntityReadPermission,
299
+ this.options
295
300
  );
296
301
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
297
- conditionsFilter = conditions.transformConditions(decision.conditions);
302
+ conditionsFilter = transformConditions(decision.conditions);
298
303
  }
299
304
  const roleEntityRef = this.getEntityReference(request, true);
300
305
  const role = await this.enforcer.getFilteredGroupingPolicy(
@@ -309,14 +314,15 @@ class PoliciesServer {
309
314
  }
310
315
  }
311
316
  );
312
- router.post(
317
+ router$1.post(
313
318
  "/roles",
314
- restInterceptor.logAuditorEvent(this.auditor),
319
+ restInterceptor.logAuditorEvent(auditor),
315
320
  async (request, response) => {
316
321
  const uniqueItems = /* @__PURE__ */ new Set();
317
- const { credentials } = await this.authorizeConditional(
322
+ const { credentials } = await authorizeConditional(
318
323
  request,
319
- pluginRbacCommon.policyEntityCreatePermission
324
+ pluginRbacCommon.policyEntityCreatePermission,
325
+ this.options
320
326
  );
321
327
  const roleRaw = request.body;
322
328
  let err = policiesValidation.validateRole(roleRaw);
@@ -364,18 +370,19 @@ class PoliciesServer {
364
370
  response.status(201).end();
365
371
  }
366
372
  );
367
- router.put(
373
+ router$1.put(
368
374
  "/roles/:kind/:namespace/:name",
369
- restInterceptor.logAuditorEvent(this.auditor),
375
+ restInterceptor.logAuditorEvent(auditor),
370
376
  async (request, response) => {
371
377
  const uniqueItems = /* @__PURE__ */ new Set();
372
378
  let conditionsFilter;
373
- const { decision, credentials } = await this.authorizeConditional(
379
+ const { decision, credentials } = await authorizeConditional(
374
380
  request,
375
- pluginRbacCommon.policyEntityUpdatePermission
381
+ pluginRbacCommon.policyEntityUpdatePermission,
382
+ this.options
376
383
  );
377
384
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
378
- conditionsFilter = conditions.transformConditions(decision.conditions);
385
+ conditionsFilter = transformConditions(decision.conditions);
379
386
  }
380
387
  const roleEntityRef = this.getEntityReference(request, true);
381
388
  const oldRoleRaw = request.body.oldRole;
@@ -490,17 +497,18 @@ class PoliciesServer {
490
497
  response.status(200).end();
491
498
  }
492
499
  );
493
- router.delete(
500
+ router$1.delete(
494
501
  "/roles/:kind/:namespace/:name",
495
- restInterceptor.logAuditorEvent(this.auditor),
502
+ restInterceptor.logAuditorEvent(auditor),
496
503
  async (request, response) => {
497
504
  let conditionsFilter;
498
- const { decision, credentials } = await this.authorizeConditional(
505
+ const { decision, credentials } = await authorizeConditional(
499
506
  request,
500
- pluginRbacCommon.policyEntityDeletePermission
507
+ pluginRbacCommon.policyEntityDeletePermission,
508
+ this.options
501
509
  );
502
510
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
503
- conditionsFilter = conditions.transformConditions(decision.conditions);
511
+ conditionsFilter = transformConditions(decision.conditions);
504
512
  }
505
513
  const roleEntityRef = this.getEntityReference(request, true);
506
514
  const currentMetadata = await this.roleMetadata.findRoleMetadata(roleEntityRef);
@@ -557,51 +565,30 @@ class PoliciesServer {
557
565
  response.status(204).end();
558
566
  }
559
567
  );
560
- router.get(
561
- "/plugins/policies",
562
- restInterceptor.logAuditorEvent(this.auditor),
563
- async (request, response) => {
564
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission);
565
- const body = await this.pluginPermMetaData.getPluginPolicies(
566
- this.options.auth
567
- );
568
- response.json(body);
569
- }
570
- );
571
- router.get(
572
- "/plugins/condition-rules",
573
- restInterceptor.logAuditorEvent(this.auditor),
574
- async (request, response) => {
575
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityReadPermission);
576
- const body = await this.pluginPermMetaData.getPluginConditionRules(
577
- this.options.auth
578
- );
579
- response.json(body);
580
- }
581
- );
582
- router.get(
568
+ router$1.get(
583
569
  "/roles/conditions",
584
- restInterceptor.logAuditorEvent(this.auditor),
570
+ restInterceptor.logAuditorEvent(auditor),
585
571
  async (request, response) => {
586
572
  let conditionsFilter;
587
- const { decision } = await this.authorizeConditional(
573
+ const { decision } = await authorizeConditional(
588
574
  request,
589
- pluginRbacCommon.policyEntityReadPermission
575
+ pluginRbacCommon.policyEntityReadPermission,
576
+ this.options
590
577
  );
591
578
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
592
- conditionsFilter = conditions.transformConditions(decision.conditions);
579
+ conditionsFilter = transformConditions(decision.conditions);
593
580
  }
594
581
  const roleMetadata = await this.roleMetadata.filterForOwnerRoleMetadata(conditionsFilter);
595
582
  const matchedRoleName = roleMetadata.flatMap((role) => {
596
583
  return role.roleEntityRef;
597
584
  });
598
- const conditions$1 = await this.conditionalStorage.filterConditions(
585
+ const conditions = await this.conditionalStorage.filterConditions(
599
586
  this.getFirstQuery(request.query.roleEntityRef),
600
587
  this.getFirstQuery(request.query.pluginId),
601
588
  this.getFirstQuery(request.query.resourceType),
602
589
  this.getActionQueries(request.query.actions)
603
590
  );
604
- const body = conditions$1.map((condition) => {
591
+ const body = conditions.map((condition) => {
605
592
  return {
606
593
  ...condition,
607
594
  permissionMapping: condition.permissionMapping.map(
@@ -614,17 +601,21 @@ class PoliciesServer {
614
601
  response.json(body);
615
602
  }
616
603
  );
617
- router.post(
604
+ router$1.post(
618
605
  "/roles/conditions",
619
- restInterceptor.logAuditorEvent(this.auditor),
606
+ restInterceptor.logAuditorEvent(auditor),
620
607
  async (request, response) => {
621
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityCreatePermission);
608
+ await authorizeConditional(
609
+ request,
610
+ pluginRbacCommon.policyEntityCreatePermission,
611
+ this.options
612
+ );
622
613
  const roleConditionPolicy = request.body;
623
614
  conditionValidation.validateRoleCondition(roleConditionPolicy);
624
615
  const conditionToCreate = await helper.processConditionMapping(
625
616
  roleConditionPolicy,
626
617
  this.pluginPermMetaData,
627
- this.options.auth
618
+ auth
628
619
  );
629
620
  const id = await this.conditionalStorage.createCondition(conditionToCreate);
630
621
  const body = { id };
@@ -632,14 +623,15 @@ class PoliciesServer {
632
623
  response.status(201).json(body);
633
624
  }
634
625
  );
635
- router.get(
626
+ router$1.get(
636
627
  "/roles/conditions/:id",
637
- restInterceptor.logAuditorEvent(this.auditor),
628
+ restInterceptor.logAuditorEvent(auditor),
638
629
  async (request, response) => {
639
630
  let conditionsFilter;
640
- const { decision } = await this.authorizeConditional(
631
+ const { decision } = await authorizeConditional(
641
632
  request,
642
- pluginRbacCommon.policyEntityReadPermission
633
+ pluginRbacCommon.policyEntityReadPermission,
634
+ this.options
643
635
  );
644
636
  const id = parseInt(request.params.id, 10);
645
637
  if (isNaN(id)) {
@@ -650,7 +642,7 @@ class PoliciesServer {
650
642
  throw new errors.NotFoundError();
651
643
  }
652
644
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
653
- conditionsFilter = conditions.transformConditions(decision.conditions);
645
+ conditionsFilter = transformConditions(decision.conditions);
654
646
  }
655
647
  const roleMetadata = await this.roleMetadata.filterForOwnerRoleMetadata(conditionsFilter);
656
648
  const matchedRoleName = roleMetadata.flatMap((role) => {
@@ -665,17 +657,18 @@ class PoliciesServer {
665
657
  response.json(body);
666
658
  }
667
659
  );
668
- router.delete(
660
+ router$1.delete(
669
661
  "/roles/conditions/:id",
670
- restInterceptor.logAuditorEvent(this.auditor),
662
+ restInterceptor.logAuditorEvent(auditor),
671
663
  async (request, response) => {
672
664
  let conditionsFilter;
673
- const { decision } = await this.authorizeConditional(
665
+ const { decision } = await authorizeConditional(
674
666
  request,
675
- pluginRbacCommon.policyEntityDeletePermission
667
+ pluginRbacCommon.policyEntityDeletePermission,
668
+ this.options
676
669
  );
677
670
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
678
- conditionsFilter = conditions.transformConditions(decision.conditions);
671
+ conditionsFilter = transformConditions(decision.conditions);
679
672
  }
680
673
  const id = parseInt(request.params.id, 10);
681
674
  if (isNaN(id)) {
@@ -700,17 +693,18 @@ class PoliciesServer {
700
693
  response.status(204).end();
701
694
  }
702
695
  );
703
- router.put(
696
+ router$1.put(
704
697
  "/roles/conditions/:id",
705
- restInterceptor.logAuditorEvent(this.auditor),
698
+ restInterceptor.logAuditorEvent(auditor),
706
699
  async (request, response) => {
707
700
  let conditionsFilter;
708
- const { decision } = await this.authorizeConditional(
701
+ const { decision } = await authorizeConditional(
709
702
  request,
710
- pluginRbacCommon.policyEntityUpdatePermission
703
+ pluginRbacCommon.policyEntityUpdatePermission,
704
+ this.options
711
705
  );
712
706
  if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
713
- conditionsFilter = conditions.transformConditions(decision.conditions);
707
+ conditionsFilter = transformConditions(decision.conditions);
714
708
  }
715
709
  const id = parseInt(request.params.id, 10);
716
710
  if (isNaN(id)) {
@@ -731,18 +725,22 @@ class PoliciesServer {
731
725
  const conditionToUpdate = await helper.processConditionMapping(
732
726
  roleConditionPolicy,
733
727
  this.pluginPermMetaData,
734
- this.options.auth
728
+ auth
735
729
  );
736
730
  await this.conditionalStorage.updateCondition(id, conditionToUpdate);
737
731
  response.locals.meta = { condition: roleConditionPolicy };
738
732
  response.status(200).end();
739
733
  }
740
734
  );
741
- router.post(
735
+ router$1.post(
742
736
  "/refresh/:id",
743
- restInterceptor.logAuditorEvent(this.auditor),
737
+ restInterceptor.logAuditorEvent(auditor),
744
738
  async (request, response) => {
745
- await this.authorizeConditional(request, pluginRbacCommon.policyEntityCreatePermission);
739
+ await authorizeConditional(
740
+ request,
741
+ pluginRbacCommon.policyEntityCreatePermission,
742
+ this.options
743
+ );
746
744
  if (!this.rbacProviders) {
747
745
  throw new errors.NotFoundError(`No RBAC providers were found`);
748
746
  }
@@ -759,8 +757,15 @@ class PoliciesServer {
759
757
  response.status(200).end();
760
758
  }
761
759
  );
762
- router.use(restInterceptor.setAuditorError());
763
- return router;
760
+ permissionDefinitionRoutes.registerPermissionDefinitionRoutes(
761
+ router$1,
762
+ this.pluginPermMetaData,
763
+ this.pluginIdProvider,
764
+ this.extraPluginsIdStorage,
765
+ this.options
766
+ );
767
+ router$1.use(restInterceptor.setAuditorError());
768
+ return router$1;
764
769
  }
765
770
  getEntityReference(request, role) {
766
771
  const kind = request.params.kind;
@@ -939,4 +944,5 @@ class PoliciesServer {
939
944
  }
940
945
 
941
946
  exports.PoliciesServer = PoliciesServer;
947
+ exports.authorizeConditional = authorizeConditional;
942
948
  //# sourceMappingURL=policies-rest-api.cjs.js.map