diff --git a/.changeset/angry-chairs-brake.md b/.changeset/angry-chairs-brake.md new file mode 100644 index 0000000000..3dd9e4bef5 --- /dev/null +++ b/.changeset/angry-chairs-brake.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-catalog-node': minor +--- + +Added the ability to inject custom permissions from modules, on `CatalogBuilder` and `CatalogPermissionExtensionPoint` diff --git a/.changeset/empty-spiders-pay.md b/.changeset/empty-spiders-pay.md new file mode 100644 index 0000000000..941823f36b --- /dev/null +++ b/.changeset/empty-spiders-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-unprocessed': patch +--- + +Internal update that injects custom permissions into the catalog using its extension point diff --git a/.changeset/famous-rats-kick.md b/.changeset/famous-rats-kick.md new file mode 100644 index 0000000000..2ca63b4717 --- /dev/null +++ b/.changeset/famous-rats-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-backend': patch +--- + +Properly forward causes of errors from upstream backends in the `PermissionIntegrationClient` diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 8b916b4770..73ef810ef2 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -40,6 +40,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", diff --git a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts index 2653ffedac..23042ff2d8 100644 --- a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts +++ b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts @@ -34,7 +34,6 @@ import { AuthorizeResult, BasicPermission, } from '@backstage/plugin-permission-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { unprocessedEntitiesDeletePermission } from '@backstage/plugin-catalog-unprocessed-entities-common'; import { NotAllowedError } from '@backstage/errors'; import { createLegacyAuthAdapters } from '@backstage/backend-common'; @@ -145,10 +144,6 @@ export class UnprocessedEntitiesModule { } registerRoutes() { - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - permissions: [unprocessedEntitiesDeletePermission], - }); - const isRequestAuthorized = async ( req: Request, permission: BasicPermission, @@ -162,8 +157,6 @@ export class UnprocessedEntitiesModule { return decision.result !== AuthorizeResult.DENY; }; - this.router.use(permissionIntegrationRouter); - this.moduleRouter .get('/entities/unprocessed/failed', async (req, res) => { return res.json( diff --git a/plugins/catalog-backend-module-unprocessed/src/module.ts b/plugins/catalog-backend-module-unprocessed/src/module.ts index 852b8fb432..dabbd60d6f 100644 --- a/plugins/catalog-backend-module-unprocessed/src/module.ts +++ b/plugins/catalog-backend-module-unprocessed/src/module.ts @@ -19,6 +19,8 @@ import { createBackendModule, } from '@backstage/backend-plugin-api'; import { UnprocessedEntitiesModule } from './UnprocessedEntitiesModule'; +import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { unprocessedEntitiesDeletePermission } from '@backstage/plugin-catalog-unprocessed-entities-common'; /** * Catalog Module for Unprocessed Entities @@ -37,6 +39,7 @@ export const catalogModuleUnprocessedEntities = createBackendModule({ httpAuth: coreServices.httpAuth, discovery: coreServices.discovery, permissions: coreServices.permissions, + catalogPermissions: catalogPermissionExtensionPoint, }, async init({ database, @@ -45,6 +48,7 @@ export const catalogModuleUnprocessedEntities = createBackendModule({ permissions, httpAuth, discovery, + catalogPermissions, }) { const module = UnprocessedEntitiesModule.create({ database: await database.getClient(), @@ -54,6 +58,8 @@ export const catalogModuleUnprocessedEntities = createBackendModule({ httpAuth, }); + catalogPermissions.addPermissions(unprocessedEntitiesDeletePermission); + module.registerRoutes(); logger.info( diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index ef2379766d..570cc5e230 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -148,6 +148,7 @@ export class CatalogBuilder { CatalogPermissionRuleInput | Array > ): this; + addPermissions(...permissions: Array>): this; addProcessor( ...processors: Array> ): CatalogBuilder; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 7fcee231fd..64516e74a9 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -84,7 +84,10 @@ import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { Config, readDurationFromConfig } from '@backstage/config'; import { Logger } from 'winston'; import { connectEntityProviders } from '../processing/connectEntityProviders'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { + Permission, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { @@ -177,6 +180,7 @@ export class CatalogBuilder { }) => Promise | void; private processingInterval: ProcessingIntervalFunction; private locationAnalyzer: LocationAnalyzer | undefined = undefined; + private readonly permissions: Permission[]; private readonly permissionRules: CatalogPermissionRuleInput[]; private allowedLocationType: string[]; private legacySingleProcessorValidation = false; @@ -200,6 +204,7 @@ export class CatalogBuilder { this.locationAnalyzers = []; this.processorsReplace = false; this.parser = undefined; + this.permissions = [...catalogPermissions]; this.permissionRules = Object.values(catalogPermissionRules); this.allowedLocationType = ['url']; @@ -401,6 +406,17 @@ export class CatalogBuilder { return this; } + /** + * Adds additional permissions. See + * {@link @backstage/plugin-permission-node#Permission}. + * + * @param permissions - Additional permissions + */ + addPermissions(...permissions: Array>) { + this.permissions.push(...permissions.flat()); + return this; + } + /** * Adds additional permission rules. Permission rules are used to evaluate * catalog resources against queries. See @@ -551,7 +567,7 @@ export class CatalogBuilder { entitiesByRef[stringifyEntityRef(parseEntityRef(resourceRef))], ); }, - permissions: catalogPermissions, + permissions: this.permissions, rules: this.permissionRules, }); diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 6e4424922d..423a9bb9d1 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -38,6 +38,7 @@ import { } from '@backstage/plugin-catalog-node'; import { loggerToWinstonLogger } from '@backstage/backend-common'; import { merge } from 'lodash'; +import { Permission } from '@backstage/plugin-permission-common'; class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint @@ -113,8 +114,13 @@ class CatalogAnalysisExtensionPointImpl class CatalogPermissionExtensionPointImpl implements CatalogPermissionExtensionPoint { + #permissions = new Array(); #permissionRules = new Array(); + addPermissions(...permission: Array>): void { + this.#permissions.push(...permission.flat()); + } + addPermissionRules( ...rules: Array< CatalogPermissionRuleInput | Array @@ -123,6 +129,10 @@ class CatalogPermissionExtensionPointImpl this.#permissionRules.push(...rules.flat()); } + get permissions() { + return this.#permissions; + } + get permissionRules() { return this.#permissionRules; } @@ -239,6 +249,7 @@ export const catalogPlugin = createBackendPlugin({ ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers); + builder.addPermissions(...permissionExtensions.permissions); builder.addPermissionRules(...permissionExtensions.permissionRules); builder.setFieldFormatValidators(modelExtensions.fieldValidators); diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index 17ad6e7c1e..fb41d6f03e 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -10,6 +10,7 @@ import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { Permission } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PlaceholderResolver } from '@backstage/plugin-catalog-node'; @@ -43,6 +44,8 @@ export interface CatalogPermissionExtensionPoint { CatalogPermissionRuleInput | Array > ): void; + // (undocumented) + addPermissions(...permissions: Array>): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 09a7e1d894..648b8c5974 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -24,7 +24,10 @@ import { PlaceholderResolver, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { + Permission, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; /** @@ -104,6 +107,7 @@ export type CatalogPermissionRuleInput< * @alpha */ export interface CatalogPermissionExtensionPoint { + addPermissions(...permissions: Array>): void; addPermissionRules( ...rules: Array< CatalogPermissionRuleInput | Array diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts index 7567dc8fbb..767c7e9dd1 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts @@ -29,6 +29,7 @@ import { BackstageCredentials, DiscoveryService, } from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; const responseSchema = z.object({ items: z.array( @@ -90,9 +91,7 @@ export class PermissionIntegrationClient { }); if (!response.ok) { - throw new Error( - `Unexpected response from plugin upstream when applying conditions. Expected 200 but got ${response.status} - ${response.statusText}`, - ); + throw await ResponseError.fromResponse(response); } const result = responseSchema.parse(await response.json()); diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index b6f6176f4d..ee2444fe89 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -162,7 +162,6 @@ const applyConditions = ( }; /** - * Takes some permission conditions and returns a definitive authorization result * on the resource to which they apply. * diff --git a/yarn.lock b/yarn.lock index 3e98dff203..1deecf22f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5631,6 +5631,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^"