diff --git a/plugins/bulk-import-backend/src/openapi.d.ts b/plugins/bulk-import-backend/src/openapi.d.ts index 443235da6b..2882acce88 100644 --- a/plugins/bulk-import-backend/src/openapi.d.ts +++ b/plugins/bulk-import-backend/src/openapi.d.ts @@ -463,3 +463,11 @@ export interface PathsDictionary { export type Client = OpenAPIClient +export type ApprovalTool = Components.Schemas.ApprovalTool; +export type Import = Components.Schemas.Import; +export type ImportRequest = Components.Schemas.ImportRequest; +export type ImportStatus = Components.Schemas.ImportStatus; +export type Organization = Components.Schemas.Organization; +export type OrganizationList = Components.Schemas.OrganizationList; +export type Repository = Components.Schemas.Repository; +export type RepositoryList = Components.Schemas.RepositoryList; diff --git a/plugins/rbac-backend-module-test/config.d.ts b/plugins/rbac-backend-module-test/config.d.ts index a3f97e2146..7843eecc4e 100644 --- a/plugins/rbac-backend-module-test/config.d.ts +++ b/plugins/rbac-backend-module-test/config.d.ts @@ -1,4 +1,4 @@ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { permission?: { @@ -7,7 +7,7 @@ export interface Config { test?: { baseUrl: string; accessToken: string; - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/rbac-backend-module-test/dist-dynamic/package.json b/plugins/rbac-backend-module-test/dist-dynamic/package.json index 906aff92c8..3a12034043 100644 --- a/plugins/rbac-backend-module-test/dist-dynamic/package.json +++ b/plugins/rbac-backend-module-test/dist-dynamic/package.json @@ -19,10 +19,6 @@ "require": "./dist/index.cjs.js", "default": "./dist/index.cjs.js" }, - "./alpha": { - "require": "./dist/alpha.cjs.js", - "default": "./dist/alpha.cjs.js" - }, "./package.json": "./package.json" }, "scripts": {}, @@ -34,8 +30,7 @@ "files": [ "dist", "config.d.ts", - "app-config.janus-idp.yaml", - "alpha" + "app-config.janus-idp.yaml" ], "configSchema": "config.d.ts", "repository": { @@ -51,11 +46,8 @@ "bugs": "https://github.com/janus-idp/backstage-plugins/issues", "bundleDependencies": true, "peerDependencies": { - "@backstage/backend-common": "^0.23.3", - "@backstage/backend-dynamic-feature-service": "^0.2.15", "@backstage/backend-plugin-api": "^0.7.0", - "@backstage/backend-tasks": "^0.5.27", - "@backstage/config": "^1.2.0" + "@backstage/errors": "^1.2.4" }, "overrides": { "@aws-sdk/util-utf8-browser": { diff --git a/plugins/rbac-backend-module-test/package.json b/plugins/rbac-backend-module-test/package.json index 744c1ded5e..42783fc4bc 100644 --- a/plugins/rbac-backend-module-test/package.json +++ b/plugins/rbac-backend-module-test/package.json @@ -16,14 +16,10 @@ }, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", "./package.json": "./package.json" }, "typesVersions": { "*": { - "alpha": [ - "src/alpha.ts" - ], "package.json": [ "package.json" ] @@ -42,25 +38,23 @@ "tsc": "tsc" }, "dependencies": { - "@backstage/backend-common": "^0.23.3", - "@backstage/backend-dynamic-feature-service": "^0.2.15", - "@backstage/backend-plugin-api": "^0.7.0", - "@backstage/backend-tasks": "^0.5.27", - "@backstage/config": "^1.2.0", "@janus-idp/backstage-plugin-rbac-node": "1.5.0", + "@backstage/backend-plugin-api": "^0.7.0", + "@backstage/errors": "^1.2.4", "csv-parse": "^5.5.6" }, "devDependencies": { + "@backstage/config": "^1.2.0", "@backstage/backend-test-utils": "0.4.4", "@backstage/cli": "0.26.11", - "@janus-idp/cli": "1.14.0" + "@janus-idp/cli": "1.14.0", + "@backstage/backend-dynamic-feature-service": "^0.2.15" }, "files": [ "dist", "config.d.ts", "dist-dynamic/*.*", "dist-dynamic/dist/**", - "dist-dynamic/alpha/*", "app-config.janus-idp.yaml" ], "configSchema": "config.d.ts", diff --git a/plugins/rbac-backend-module-test/src/alpha.ts b/plugins/rbac-backend-module-test/src/alpha.ts deleted file mode 100644 index cd2637f39d..0000000000 --- a/plugins/rbac-backend-module-test/src/alpha.ts +++ /dev/null @@ -1 +0,0 @@ -export { rbacModuleTest as default } from './module'; diff --git a/plugins/rbac-backend-module-test/src/dynamic/index.ts b/plugins/rbac-backend-module-test/src/dynamic/index.ts deleted file mode 100644 index cf68b8a590..0000000000 --- a/plugins/rbac-backend-module-test/src/dynamic/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BackendDynamicPluginInstaller } from '@backstage/backend-dynamic-feature-service'; - -export const dynamicPluginInstaller: BackendDynamicPluginInstaller = { - kind: 'legacy', -}; diff --git a/plugins/rbac-backend-module-test/src/index.ts b/plugins/rbac-backend-module-test/src/index.ts index afa9cba3f8..ab824e38e7 100644 --- a/plugins/rbac-backend-module-test/src/index.ts +++ b/plugins/rbac-backend-module-test/src/index.ts @@ -3,6 +3,4 @@ * * @packageDocumentation */ - -export * from './dynamic/index'; -export * from './module'; +export { rbacModuleTest as default } from './module'; diff --git a/plugins/rbac-backend-module-test/src/module.ts b/plugins/rbac-backend-module-test/src/module.ts index d1d66815d9..d465661e19 100644 --- a/plugins/rbac-backend-module-test/src/module.ts +++ b/plugins/rbac-backend-module-test/src/module.ts @@ -25,14 +25,16 @@ export const rbacModuleTest = createBackendModule({ }, async init({ logger, rbac, scheduler, config }) { rbac.addRBACProvider( - TestProvider.fromConfig(config, { - logger, - scheduler: scheduler, - schedule: scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - }), + TestProvider.fromConfig( + { config, logger }, + { + scheduler: scheduler, + schedule: scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), + }, + ), ); }, }); diff --git a/plugins/rbac-backend-module-test/src/provider/TestProvider.ts b/plugins/rbac-backend-module-test/src/provider/TestProvider.ts index 335da22623..067a659604 100644 --- a/plugins/rbac-backend-module-test/src/provider/TestProvider.ts +++ b/plugins/rbac-backend-module-test/src/provider/TestProvider.ts @@ -5,11 +5,12 @@ import { SchedulerServiceTaskRunner, SchedulerServiceTaskScheduleDefinition, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; +import { InputError, isError, NotFoundError } from '@backstage/errors'; import { parse } from 'csv-parse/sync'; -import { +import type { RBACProvider, RBACProviderConnection, } from '@janus-idp/backstage-plugin-rbac-node'; @@ -28,27 +29,30 @@ export class TestProvider implements RBACProvider { private connection?: RBACProviderConnection; static fromConfig( - config: Config, - options: { + deps: { + config: Config; logger: LoggerService; - schedule?: SchedulerServiceTaskRunner; - scheduler?: SchedulerService; }, + options: + | { schedule: SchedulerServiceTaskRunner } + | { scheduler: SchedulerService }, ): TestProvider { - const providerConfig = readProviderConfig(config); + const providerConfig = readProviderConfig(deps.config); let schedulerServiceTaskRunner; - if (options.scheduler && providerConfig.schedule) { + if ('scheduler' in options && providerConfig.schedule) { schedulerServiceTaskRunner = options.scheduler.createScheduledTaskRunner( providerConfig.schedule, ); - } else if (options.schedule) { + } else if ('schedule' in options) { schedulerServiceTaskRunner = options.schedule; } else { - throw new Error('Neither schedule nor scheduler is provided.'); + throw new InputError( + `No schedule provided via config for RBACTestProvider.`, + ); } - return new TestProvider(schedulerServiceTaskRunner, options.logger); + return new TestProvider(schedulerServiceTaskRunner, deps.logger); } private constructor( @@ -89,8 +93,10 @@ export class TestProvider implements RBACProvider { fn: async () => { try { await this.run(); - } catch (error: any) { - this.logger.error(`Error occurred, here is the error ${error}`); + } catch (error) { + if (isError(error)) { + this.logger.error(`Error occurred, here is the error ${error}`); + } } }, }); @@ -99,7 +105,7 @@ export class TestProvider implements RBACProvider { private async run(): Promise { if (!this.connection) { - throw new Error('Not initialized'); + throw new NotFoundError('Not initialized'); } const permissions: string[][] = []; const roles: string[][] = []; diff --git a/plugins/rbac-backend/README.md b/plugins/rbac-backend/README.md index c96c15f4f1..1a0c9aa4e9 100644 --- a/plugins/rbac-backend/README.md +++ b/plugins/rbac-backend/README.md @@ -38,64 +38,6 @@ yarn workspace backend add @janus-idp/backstage-plugin-rbac-backend ### Configuring the Backend -#### Old Backend System - -To connect the RBAC framework to your backend use the `PolicyBuilder` class in your backend permissions plugin (typically `packages/backend/src/plugins/permissions.ts`) as follows: - -```ts -/* highlight-add-start */ -import { Router } from 'express'; - -import { - PluginIdProvider, - PolicyBuilder, -} from '@janus-idp/backstage-plugin-rbac-backend'; - -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, - pluginIdProvider: PluginIdProvider, -): Promise { - return PolicyBuilder.build( - { - config: env.config, - logger: env.logger, - discovery: env.discovery, - identity: env.identity, - permissions: env.permissions, - }, - pluginIdProvider, - ); -} -/* highlight-add-end */ -``` - -Secondly, in your backend router (typically `packages/backend/src/index.ts`) add a route for `/permission` specifying the list of plugin id's that support permissions: - -```ts -// ... -/* highlight-add-next-line */ -import permission from './plugins/permissions'; - -async function main() { - // ... - /* highlight-add-next-line */ - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - - // ... - /* highlight-add-start */ - apiRouter.use( - '/permission', - await permission(permissionEnv, { - // return list static plugin which supports Backstage permissions. - getPluginIds: () => ['catalog', 'scaffolder', 'permission'], - }), - ); - /* highlight-add-end */ -} -``` - #### New Backend System The RBAC plugin supports the integration with the new backend system. diff --git a/plugins/rbac-backend/package.json b/plugins/rbac-backend/package.json index 4cdccb927b..66ee077428 100644 --- a/plugins/rbac-backend/package.json +++ b/plugins/rbac-backend/package.json @@ -36,14 +36,11 @@ "@backstage/backend-plugin-api": "^0.7.0", "@backstage/catalog-client": "^1.6.5", "@backstage/catalog-model": "^1.5.0", - "@backstage/config": "^1.2.0", - "@backstage/core-plugin-api": "^1.9.3", "@backstage/errors": "^1.2.4", "@backstage/plugin-auth-node": "^0.4.17", "@backstage/plugin-permission-backend": "^0.5.46", "@backstage/plugin-permission-common": "^0.8.0", "@backstage/plugin-permission-node": "^0.8.0", - "@backstage/types": "^1.1.1", "@dagrejs/graphlib": "^2.1.13", "@janus-idp/backstage-plugin-audit-log-node": "1.5.0", "@janus-idp/backstage-plugin-rbac-common": "1.10.0", @@ -55,9 +52,7 @@ "js-yaml": "^4.1.0", "knex": "^3.0.0", "lodash": "^4.17.21", - "qs": "^6.11.2", - "typeorm-adapter": "^1.6.1", - "yn": "^4.0.0" + "typeorm-adapter": "^1.6.1" }, "devDependencies": { "@backstage/backend-test-utils": "0.4.4", @@ -67,7 +62,11 @@ "@types/supertest": "2.0.16", "knex-mock-client": "2.0.1", "msw": "1.3.3", - "supertest": "6.3.4" + "supertest": "6.3.4", + "@backstage/core-plugin-api": "^1.9.3", + "@backstage/types": "^1.1.1", + "qs": "^6.11.2", + "@backstage/config": "^1.2.0" }, "files": [ "dist", diff --git a/plugins/rbac-backend/src/audit-log/audit-logger.ts b/plugins/rbac-backend/src/audit-log/audit-logger.ts index 4520057075..2d80ad6502 100644 --- a/plugins/rbac-backend/src/audit-log/audit-logger.ts +++ b/plugins/rbac-backend/src/audit-log/audit-logger.ts @@ -3,9 +3,9 @@ import { PolicyDecision, ResourcePermission, } from '@backstage/plugin-permission-common'; -import { PolicyQuery } from '@backstage/plugin-permission-node'; +import type { PolicyQuery } from '@backstage/plugin-permission-node'; -import { AuditLogOptions } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { AuditLogOptions } from '@janus-idp/backstage-plugin-audit-log-node'; import { PermissionAction, RoleConditionalPolicyDecision, diff --git a/plugins/rbac-backend/src/audit-log/rest-errors-interceptor.ts b/plugins/rbac-backend/src/audit-log/rest-errors-interceptor.ts index 8d651519c1..0c85d00680 100644 --- a/plugins/rbac-backend/src/audit-log/rest-errors-interceptor.ts +++ b/plugins/rbac-backend/src/audit-log/rest-errors-interceptor.ts @@ -1,6 +1,11 @@ -import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; +import type { + ErrorRequestHandler, + NextFunction, + Request, + Response, +} from 'express'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; import { ConditionEvents, diff --git a/plugins/rbac-backend/src/conditional-aliases/alias-resolver.test.ts b/plugins/rbac-backend/src/conditional-aliases/alias-resolver.test.ts index f9352d08a1..c7d7cb78b0 100644 --- a/plugins/rbac-backend/src/conditional-aliases/alias-resolver.test.ts +++ b/plugins/rbac-backend/src/conditional-aliases/alias-resolver.test.ts @@ -1,4 +1,4 @@ -import { +import type { PermissionCondition, PermissionCriteria, PermissionRuleParams, diff --git a/plugins/rbac-backend/src/conditional-aliases/alias-resolver.ts b/plugins/rbac-backend/src/conditional-aliases/alias-resolver.ts index e58d118ebc..611225e45f 100644 --- a/plugins/rbac-backend/src/conditional-aliases/alias-resolver.ts +++ b/plugins/rbac-backend/src/conditional-aliases/alias-resolver.ts @@ -1,11 +1,11 @@ -import { BackstageUserInfo } from '@backstage/backend-plugin-api'; -import { +import type { BackstageUserInfo } from '@backstage/backend-plugin-api'; +import type { PermissionCondition, PermissionCriteria, PermissionRuleParam, PermissionRuleParams, } from '@backstage/plugin-permission-common'; -import { JsonPrimitive } from '@backstage/types'; +import type { JsonPrimitive } from '@backstage/types'; import { CONDITION_ALIAS_SIGN, diff --git a/plugins/rbac-backend/src/database/casbin-adapter-factory.test.ts b/plugins/rbac-backend/src/database/casbin-adapter-factory.test.ts index 0f8a136c47..520121d69d 100644 --- a/plugins/rbac-backend/src/database/casbin-adapter-factory.test.ts +++ b/plugins/rbac-backend/src/database/casbin-adapter-factory.test.ts @@ -1,4 +1,4 @@ -import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; import knex, { Knex } from 'knex'; import TypeORMAdapter from 'typeorm-adapter'; @@ -28,11 +28,13 @@ describe('CasbinAdapterFactory', () => { client: 'better-sqlite3', connection: ':memory', }); - const config = new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, }, }); @@ -54,16 +56,18 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - schema: 'public', - user: 'postgresUser', - password: process.env.TEST, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + schema: 'public', + user: 'postgresUser', + password: process.env.TEST, + }, }, }, }, @@ -84,17 +88,19 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration with enabled ssl.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - schema: 'public', - user: 'postgresUser', - password: process.env.TEST, - ssl: true, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + schema: 'public', + user: 'postgresUser', + password: process.env.TEST, + ssl: true, + }, }, }, }, @@ -115,17 +121,19 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration with intentionally disabled ssl.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - schema: 'public', - user: 'postgresUser', - password: process.env.TEST, - ssl: false, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + schema: 'public', + user: 'postgresUser', + password: process.env.TEST, + ssl: false, + }, }, }, }, @@ -146,18 +154,20 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration with intentionally ssl and ca cert.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - schema: 'public', - user: 'postgresUser', - password: process.env.TEST, - ssl: { - ca: 'abc', + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + schema: 'public', + user: 'postgresUser', + password: process.env.TEST, + ssl: { + ca: 'abc', + }, }, }, }, @@ -181,18 +191,20 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration with intentionally ssl and TLS options.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - user: 'postgresUser', - password: process.env.TEST, - ssl: { - ca: 'abc', - rejectUnauthorized: false, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + user: 'postgresUser', + password: process.env.TEST, + ssl: { + ca: 'abc', + rejectUnauthorized: false, + }, }, }, }, @@ -217,17 +229,19 @@ describe('CasbinAdapterFactory', () => { }); it('test building an adapter using a PostgreSQL configuration with intentionally ssl without CA.', async () => { - const config = new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - user: 'postgresUser', - password: process.env.TEST, - ssl: { - rejectUnauthorized: false, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + user: 'postgresUser', + password: process.env.TEST, + ssl: { + rejectUnauthorized: false, + }, }, }, }, @@ -254,10 +268,12 @@ describe('CasbinAdapterFactory', () => { it('ensure that building an adapter with an unknown configuration fails.', async () => { const client = 'unknown-db'; const expectedError = new Error(`Unsupported database client ${client}`); - const config = new ConfigReader({ - backend: { - database: { - client, + const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client, + }, }, }, }); diff --git a/plugins/rbac-backend/src/database/casbin-adapter-factory.ts b/plugins/rbac-backend/src/database/casbin-adapter-factory.ts index 6976e9db2b..b4bd5389b4 100644 --- a/plugins/rbac-backend/src/database/casbin-adapter-factory.ts +++ b/plugins/rbac-backend/src/database/casbin-adapter-factory.ts @@ -1,11 +1,11 @@ -import { Config } from '@backstage/config'; -import { ConfigApi } from '@backstage/core-plugin-api'; +import type { Config } from '@backstage/config'; +import type { ConfigApi } from '@backstage/core-plugin-api'; import { Knex } from 'knex'; import TypeORMAdapter from 'typeorm-adapter'; import { resolve } from 'path'; -import { ConnectionOptions, TlsOptions } from 'tls'; +import type { ConnectionOptions, TlsOptions } from 'tls'; import '@backstage/backend-defaults/database'; diff --git a/plugins/rbac-backend/src/database/conditional-storage.ts b/plugins/rbac-backend/src/database/conditional-storage.ts index 1a7a63b66f..6c130f87a8 100644 --- a/plugins/rbac-backend/src/database/conditional-storage.ts +++ b/plugins/rbac-backend/src/database/conditional-storage.ts @@ -3,7 +3,7 @@ import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { Knex } from 'knex'; -import { +import type { PermissionAction, PermissionInfo, RoleConditionalPolicyDecision, diff --git a/plugins/rbac-backend/src/database/conditional.storage.test.ts b/plugins/rbac-backend/src/database/conditional.storage.test.ts index c287d79750..e07c2012e8 100644 --- a/plugins/rbac-backend/src/database/conditional.storage.test.ts +++ b/plugins/rbac-backend/src/database/conditional.storage.test.ts @@ -1,11 +1,14 @@ -import { PluginDatabaseManager } from '@backstage/backend-common'; -import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { + mockServices, + TestDatabaseId, + TestDatabases, +} from '@backstage/backend-test-utils'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import * as Knex from 'knex'; import { createTracker, MockClient } from 'knex-mock-client'; -import { +import type { PermissionInfo, RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; @@ -83,14 +86,12 @@ describe('DataBaseConditionalStorage', () => { async function createDatabase(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); - const databaseManagerMock: PluginDatabaseManager = { - getClient: jest.fn(() => { - return Promise.resolve(knex); - }), + const mockDatabaseService = mockServices.database.mock({ + getClient: async () => knex, migrations: { skip: false }, - }; + }); - await migrate(databaseManagerMock); + await migrate(mockDatabaseService); return { knex, db: new DataBaseConditionalStorage(knex), diff --git a/plugins/rbac-backend/src/database/migration.ts b/plugins/rbac-backend/src/database/migration.ts index 732538fdb6..5578e9a9c4 100644 --- a/plugins/rbac-backend/src/database/migration.ts +++ b/plugins/rbac-backend/src/database/migration.ts @@ -1,14 +1,14 @@ import { - PluginDatabaseManager, + DatabaseService, resolvePackagePath, -} from '@backstage/backend-common'; +} from '@backstage/backend-plugin-api'; const migrationsDir = resolvePackagePath( '@janus-idp/backstage-plugin-rbac-backend', // Package name 'migrations', // Migrations directory ); -export async function migrate(databaseManager: PluginDatabaseManager) { +export async function migrate(databaseManager: DatabaseService) { const knex = await databaseManager.getClient(); if (!databaseManager.migrations?.skip) { diff --git a/plugins/rbac-backend/src/database/role-metadata.test.ts b/plugins/rbac-backend/src/database/role-metadata.test.ts index d448aa6b11..4695316399 100644 --- a/plugins/rbac-backend/src/database/role-metadata.test.ts +++ b/plugins/rbac-backend/src/database/role-metadata.test.ts @@ -1,5 +1,8 @@ -import { PluginDatabaseManager } from '@backstage/backend-common'; -import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { + mockServices, + TestDatabaseId, + TestDatabases, +} from '@backstage/backend-test-utils'; import * as Knex from 'knex'; import { createTracker, MockClient } from 'knex-mock-client'; @@ -21,13 +24,12 @@ describe('role-metadata-db-table', () => { async function createDatabase(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); - const databaseManagerMock: PluginDatabaseManager = { - getClient: jest.fn(() => { - return Promise.resolve(knex); - }), + const mockDatabaseService = mockServices.database.mock({ + getClient: async () => knex, migrations: { skip: false }, - }; - await migrate(databaseManagerMock); + }); + + await migrate(mockDatabaseService); return { knex, db: new DataBaseRoleMetadataStorage(knex), diff --git a/plugins/rbac-backend/src/database/role-metadata.ts b/plugins/rbac-backend/src/database/role-metadata.ts index cfc563731f..e80f463d8a 100644 --- a/plugins/rbac-backend/src/database/role-metadata.ts +++ b/plugins/rbac-backend/src/database/role-metadata.ts @@ -2,7 +2,10 @@ import { ConflictError, InputError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; -import { RoleMetadata, Source } from '@janus-idp/backstage-plugin-rbac-common'; +import type { + RoleMetadata, + Source, +} from '@janus-idp/backstage-plugin-rbac-common'; import { deepSortedEqual } from '../helper'; diff --git a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts index 6cf7c0008b..190bb9a46a 100644 --- a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts +++ b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.test.ts @@ -1,6 +1,6 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { Adapter, @@ -12,7 +12,7 @@ import { import * as Knex from 'knex'; import { MockClient } from 'knex-mock-client'; -import { Source } from '@janus-idp/backstage-plugin-rbac-common'; +import type { Source } from '@janus-idp/backstage-plugin-rbac-common'; import { resolve } from 'path'; @@ -54,6 +54,8 @@ const configPermission = [ const configRole = ['user:default/guest', 'role:default/config']; +// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' +// once '@backstage/plugin-catalog-node' is upgraded const catalogApi = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), @@ -71,11 +73,7 @@ const catalogApi = { getLocationByEntity: jest.fn().mockImplementation(), }; -const loggerMock: any = { - warn: jest.fn().mockImplementation(), - debug: jest.fn().mockImplementation(), - info: jest.fn().mockImplementation(), -}; +const loggerMock = mockServices.logger.mock(); const modifiedBy = 'user:default/some-admin'; @@ -155,7 +153,7 @@ describe('CSVFileWatcher', () => { './../__fixtures__/data/valid-csv/rbac-policy.csv', ); - const config = newConfigReader(); + const config = newConfig(); const adapter = await new CasbinDBAdapterFactory( config, @@ -697,7 +695,7 @@ async function createEnforcer( const rbacDBClient = Knex.knex({ client: MockClient }); const enf = await newEnforcer(theModel, adapter); - const config = newConfigReader(); + const config = newConfig(); const rm = new BackstageRoleManager( catalogApi, @@ -714,10 +712,10 @@ async function createEnforcer( return enf; } -function newConfigReader( +function newConfig( users?: Array<{ name: string }>, superUsers?: Array<{ name: string }>, -): ConfigReader { +): Config { const testUsers = [ { name: 'user:default/guest', @@ -727,19 +725,21 @@ function newConfigReader( }, ]; - return new ConfigReader({ - permission: { - rbac: { - admin: { - users: users || testUsers, - superUsers: superUsers, + return mockServices.rootConfig({ + data: { + permission: { + rbac: { + admin: { + users: users || testUsers, + superUsers: superUsers, + }, }, }, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, }, }); diff --git a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.ts b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.ts index 82d680ba57..5c1070a65e 100644 --- a/plugins/rbac-backend/src/file-permissions/csv-file-watcher.ts +++ b/plugins/rbac-backend/src/file-permissions/csv-file-watcher.ts @@ -1,10 +1,10 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { Enforcer, FileAdapter, newEnforcer, newModelFromString } from 'casbin'; import { parse } from 'csv-parse/sync'; import { difference } from 'lodash'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; import { HANDLE_RBAC_DATA_STAGE, diff --git a/plugins/rbac-backend/src/file-permissions/file-watcher.ts b/plugins/rbac-backend/src/file-permissions/file-watcher.ts index 6414ea741d..9e0ef78470 100644 --- a/plugins/rbac-backend/src/file-permissions/file-watcher.ts +++ b/plugins/rbac-backend/src/file-permissions/file-watcher.ts @@ -1,4 +1,4 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import chokidar from 'chokidar'; diff --git a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts index 218110c143..606eb3a133 100644 --- a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts +++ b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.test.ts @@ -1,6 +1,6 @@ import { mockServices } from '@backstage/backend-test-utils'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { MetadataResponse } from '@backstage/plugin-permission-node'; +import type { MetadataResponse } from '@backstage/plugin-permission-node'; import { resolve } from 'path'; @@ -14,11 +14,7 @@ import { RoleEventEmitter, RoleEvents } from '../service/enforcer-delegate'; import { PluginPermissionMetadataCollector } from '../service/plugin-endpoints'; import { YamlConditinalPoliciesFileWatcher } from './yaml-conditional-file-watcher'; // Adjust the import path as necessary -const loggerMock: any = { - warn: jest.fn().mockImplementation(), - debug: jest.fn().mockImplementation(), - info: jest.fn().mockImplementation(), -}; +const loggerMock = mockServices.logger.mock(); let loggerWarnSpy: jest.SpyInstance; diff --git a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.ts b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.ts index 0f0f662b12..40a810a4d3 100644 --- a/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.ts +++ b/plugins/rbac-backend/src/file-permissions/yaml-conditional-file-watcher.ts @@ -1,10 +1,10 @@ -import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; +import type { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import yaml from 'js-yaml'; import { omit } from 'lodash'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; -import { +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { PermissionAction, RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; diff --git a/plugins/rbac-backend/src/helper.ts b/plugins/rbac-backend/src/helper.ts index 0bd7c3b9c4..d4c69d31ab 100644 --- a/plugins/rbac-backend/src/helper.ts +++ b/plugins/rbac-backend/src/helper.ts @@ -1,5 +1,5 @@ import { AuthService } from '@backstage/backend-plugin-api'; -import { MetadataResponse } from '@backstage/plugin-permission-node'; +import type { MetadataResponse } from '@backstage/plugin-permission-node'; import { difference, diff --git a/plugins/rbac-backend/src/providers/connect-providers.test.ts b/plugins/rbac-backend/src/providers/connect-providers.test.ts index a810f41886..8d9ff65d1d 100644 --- a/plugins/rbac-backend/src/providers/connect-providers.test.ts +++ b/plugins/rbac-backend/src/providers/connect-providers.test.ts @@ -1,6 +1,5 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; import { Adapter, @@ -12,7 +11,7 @@ import { import * as Knex from 'knex'; import { MockClient } from 'knex-mock-client'; -import { +import type { RBACProvider, RBACProviderConnection, } from '@janus-idp/backstage-plugin-rbac-node'; @@ -91,6 +90,8 @@ const auditLoggerMock = { auditLog: jest.fn().mockImplementation(() => Promise.resolve()), }; +// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' +// once '@backstage/plugin-catalog-node' is upgraded const catalogApi = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), @@ -138,15 +139,26 @@ const existingPolicy = [ ['role:default/existing-provider-role', 'catalog-entity', 'read', 'allow'], ]; +const config = mockServices.rootConfig({ + data: { + permission: { + rbac: {}, + }, + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }, +}); + describe('Connection', () => { let provider: Connection; let enforcerDelegate: EnforcerDelegate; beforeEach(async () => { const id = 'test'; - - const config = newConfigReader(); - const adapter = await new CasbinDBAdapterFactory( config, dbManagerMock, @@ -436,7 +448,6 @@ describe('connectRBACProviders', () => { >; it('should initialize rbac providers', async () => { connectSpy = jest.spyOn(providerMock, 'connect'); - const config = newConfigReader(); const adapter = await new CasbinDBAdapterFactory( config, @@ -466,20 +477,6 @@ describe('connectRBACProviders', () => { }); }); -function newConfigReader(): ConfigReader { - return new ConfigReader({ - permission: { - rbac: {}, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - }, - }, - }); -} - async function createEnforcer( theModel: Model, adapter: Adapter, @@ -489,8 +486,6 @@ async function createEnforcer( const rbacDBClient = Knex.knex({ client: MockClient }); const enf = await newEnforcer(theModel, adapter); - const config = newConfigReader(); - const rm = new BackstageRoleManager( catalogApi, logger, diff --git a/plugins/rbac-backend/src/providers/connect-providers.ts b/plugins/rbac-backend/src/providers/connect-providers.ts index ff200ede3d..c4827682d7 100644 --- a/plugins/rbac-backend/src/providers/connect-providers.ts +++ b/plugins/rbac-backend/src/providers/connect-providers.ts @@ -1,4 +1,4 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { Enforcer, @@ -7,8 +7,8 @@ import { StringAdapter, } from 'casbin'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; -import { +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { RBACProvider, RBACProviderConnection, } from '@janus-idp/backstage-plugin-rbac-node'; diff --git a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts index e5d0080226..ed6de0638c 100644 --- a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts +++ b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.test.ts @@ -1,5 +1,5 @@ import { mockServices } from '@backstage/backend-test-utils'; -import { Entity, GroupEntity } from '@backstage/catalog-model'; +import type { Entity, GroupEntity } from '@backstage/catalog-model'; import * as Knex from 'knex'; import { createTracker, MockClient, Tracker } from 'knex-mock-client'; @@ -54,6 +54,8 @@ describe('ancestor-search-memo', () => { const testUserGroups = [createGroupEntity('team-a', 'team-b', [], ['adam'])]; + // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' + // once '@backstage/plugin-catalog-node' is upgraded const catalogApiMock: any = { getEntities: jest.fn().mockImplementation((arg: any) => { const hasMember = arg.filter['relations.hasMember']; diff --git a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts index 990ecde44d..f3bafff23b 100644 --- a/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts +++ b/plugins/rbac-backend/src/role-manager/ancestor-search-memo.ts @@ -1,6 +1,6 @@ -import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; +import type { AuthService, LoggerService } from '@backstage/backend-plugin-api'; +import type { CatalogApi } from '@backstage/catalog-client'; +import type { Entity } from '@backstage/catalog-model'; import { alg, Graph } from '@dagrejs/graphlib'; import { Knex } from 'knex'; diff --git a/plugins/rbac-backend/src/role-manager/role-manager.test.ts b/plugins/rbac-backend/src/role-manager/role-manager.test.ts index 05fba0c48c..48874390e8 100644 --- a/plugins/rbac-backend/src/role-manager/role-manager.test.ts +++ b/plugins/rbac-backend/src/role-manager/role-manager.test.ts @@ -1,8 +1,8 @@ -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { CatalogApi } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; -import { ConfigReader } from '@backstage/config'; +import type { CatalogApi } from '@backstage/catalog-client'; +import type { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import * as Knex from 'knex'; import { createTracker, MockClient, Tracker } from 'knex-mock-client'; @@ -12,14 +12,13 @@ import { BackstageRoleManager } from '../role-manager/role-manager'; describe('BackstageRoleManager', () => { const catalogDBClient = Knex.knex({ client: MockClient }); const rbacDBClient = Knex.knex({ client: MockClient }); + // TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' + // once '@backstage/plugin-catalog-node' is upgraded const catalogApiMock: any = { getEntities: jest.fn().mockImplementation(), }; - const loggerMock: any = { - warn: jest.fn().mockImplementation(), - debug: jest.fn().mockImplementation(), - }; + const loggerMock = mockServices.logger.mock(); const mockAuthService = mockServices.auth(); @@ -29,7 +28,7 @@ describe('BackstageRoleManager', () => { .fn() .mockImplementation(() => Promise.resolve({ items: [] })); - const config = newConfigReader(); + const config = newConfig(); roleManager = new BackstageRoleManager( catalogApiMock as CatalogApi, @@ -49,7 +48,7 @@ describe('BackstageRoleManager', () => { it('should throw an error whenever max depth is less than 0', () => { let expectedError; let errorRoleManager; - const config = newConfigReader(-1); + const config = newConfig(-1); try { errorRoleManager = new BackstageRoleManager( @@ -341,7 +340,7 @@ describe('BackstageRoleManager', () => { // it('should disable group inheritance when max-depth=0', async () => { // max-depth=0 - const config = newConfigReader(0); + const config = newConfig(0); const rm = new BackstageRoleManager( catalogApiMock as CatalogApi, loggerMock as LoggerService, @@ -1086,7 +1085,7 @@ describe('BackstageRoleManager', () => { // user:default/mike -------------------|---------------------------------| // it('should return false for hasLink, when user:default/mike inherits role from group tree with group:default/team-e, complex tree, maxDepth of 3', async () => { - const config = newConfigReader(1); + const config = newConfig(1); const roleManagerMaxDepth = new BackstageRoleManager( catalogApiMock as CatalogApi, @@ -1724,11 +1723,11 @@ describe('BackstageRoleManager', () => { } }); -function newConfigReader( +function newConfig( maxDepth?: number, users?: Array<{ name: string }>, superUsers?: Array<{ name: string }>, -): ConfigReader { +): Config { const testUsers = [ { name: 'user:default/guest', @@ -1738,20 +1737,22 @@ function newConfigReader( }, ]; - return new ConfigReader({ - permission: { - rbac: { - admin: { - users: users || testUsers, - superUsers: superUsers, + return mockServices.rootConfig({ + data: { + permission: { + rbac: { + admin: { + users: users || testUsers, + superUsers: superUsers, + }, + maxDepth, }, - maxDepth, }, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, }, }); diff --git a/plugins/rbac-backend/src/role-manager/role-manager.ts b/plugins/rbac-backend/src/role-manager/role-manager.ts index a5bb239a65..484c7eadb8 100644 --- a/plugins/rbac-backend/src/role-manager/role-manager.ts +++ b/plugins/rbac-backend/src/role-manager/role-manager.ts @@ -1,7 +1,7 @@ -import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import type { AuthService, LoggerService } from '@backstage/backend-plugin-api'; +import type { CatalogApi } from '@backstage/catalog-client'; import { parseEntityRef } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { RoleManager } from 'casbin'; import { Knex } from 'knex'; diff --git a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts index 266bd28b64..d810b7e680 100644 --- a/plugins/rbac-backend/src/service/enforcer-delegate.test.ts +++ b/plugins/rbac-backend/src/service/enforcer-delegate.test.ts @@ -1,6 +1,4 @@ -import { getVoidLogger } from '@backstage/backend-common'; import { mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; import { newEnforcer, newModelFromString } from 'casbin'; import * as Knex from 'knex'; @@ -15,6 +13,8 @@ import { BackstageRoleManager } from '../role-manager/role-manager'; import { EnforcerDelegate } from './enforcer-delegate'; import { MODEL } from './permission-model'; +// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' +// once '@backstage/plugin-catalog-node' is upgraded const catalogApi = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), @@ -44,15 +44,17 @@ const dbManagerMock = Knex.knex({ client: MockClient }); const mockAuthService = mockServices.auth(); -const config = new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', +const config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + rbac: {}, }, - }, - permission: { - rbac: {}, }, }); const policy = ['user:default/tom', 'policy-entity', 'read', 'allow']; @@ -120,7 +122,7 @@ describe('EnforcerDelegate', () => { groupingPolicies?: string[][], ): Promise { const theModel = newModelFromString(MODEL); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const sqliteInMemoryAdapter = await new CasbinDBAdapterFactory( config, diff --git a/plugins/rbac-backend/src/service/permission-policy.test.ts b/plugins/rbac-backend/src/service/permission-policy.test.ts index 4d95f002bf..5f646c5977 100644 --- a/plugins/rbac-backend/src/service/permission-policy.test.ts +++ b/plugins/rbac-backend/src/service/permission-policy.test.ts @@ -1,13 +1,12 @@ -import { getVoidLogger } from '@backstage/backend-common'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import type { LoggerService } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { Entity } from '@backstage/catalog-model'; -import { ConfigReader } from '@backstage/config'; +import type { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { AuthorizeResult, createPermission, } from '@backstage/plugin-permission-common'; -import { +import type { PolicyQuery, PolicyQueryUser, } from '@backstage/plugin-permission-node'; @@ -23,7 +22,7 @@ import { import * as Knex from 'knex'; import { MockClient } from 'knex-mock-client'; -import { RoleMetadata } from '@janus-idp/backstage-plugin-rbac-common'; +import type { RoleMetadata } from '@janus-idp/backstage-plugin-rbac-common'; import { resolve } from 'path'; @@ -41,6 +40,8 @@ import { PluginPermissionMetadataCollector } from './plugin-endpoints'; type PermissionAction = 'create' | 'read' | 'update' | 'delete'; +// TODO: Move to 'catalogServiceMock' from '@backstage/plugin-catalog-node/testUtils' +// once '@backstage/plugin-catalog-node' is upgraded const catalogApi = { getEntityAncestors: jest.fn().mockImplementation(), getLocationById: jest.fn().mockImplementation(), @@ -118,7 +119,7 @@ describe('RBACPermissionPolicy Tests', () => { }); it('should build', async () => { - const config = newConfigReader(); + const config = newConfig(); const adapter = await newAdapter(config); const enfDelegate = await newEnforcerDelegate(adapter, config); @@ -135,7 +136,7 @@ describe('RBACPermissionPolicy Tests', () => { }); const stringPolicy = `p, user:default/known_user, test-resource, update, allow `; - const config = newConfigReader(); + const config = newConfig(); const adapter = await newAdapter(config, stringPolicy); const enfDelegate = await newEnforcerDelegate(adapter, config); @@ -149,7 +150,7 @@ describe('RBACPermissionPolicy Tests', () => { let policy: RBACPermissionPolicy; beforeEach(async () => { - const config = newConfigReader(); + const config = newConfig(); const adapter = await newAdapter(config); enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy(config, enfDelegate); @@ -251,7 +252,7 @@ describe('RBACPermissionPolicy Tests', () => { }); describe('Policy checks for clean up old policies for csv file', () => { - let config: ConfigReader; + let config: Config; let adapter: Adapter; let enforcerDelegate: EnforcerDelegate; let rbacPolicy: RBACPermissionPolicy; @@ -291,7 +292,7 @@ describe('RBACPermissionPolicy Tests', () => { beforeEach(async () => { (roleMetadataStorageMock.removeRoleMetadata as jest.Mock).mockReset(); - config = newConfigReader(); + config = newConfig(); adapter = await newAdapter(config); catalogApi.getEntities.mockReturnValue({ items: [] }); @@ -608,7 +609,7 @@ describe('RBACPermissionPolicy Tests', () => { __dirname, './../__fixtures__/data/valid-csv/basic-and-resource-policies.csv', ); - const config = newConfigReader(basicAndResourcePermissions); + const config = newConfig(basicAndResourcePermissions); const adapter = await newAdapter(config); enfDelegate = await newEnforcerDelegate(adapter, config); @@ -915,7 +916,7 @@ describe('RBACPermissionPolicy Tests', () => { }, ); - const config = newConfigReader(csvPermFile, admins, superUser); + const config = newConfig(csvPermFile, admins, superUser); const adapter = await newAdapter(config); enfDelegate = await newEnforcerDelegate(adapter, config); @@ -953,17 +954,19 @@ describe('RBACPermissionPolicy Tests', () => { throw new Error(`Failed to create`); }); - const config = new ConfigReader({ - permission: { - rbac: { - 'policies-csv-file': csvPermFile, - policyFileReload: true, + const config = mockServices.rootConfig({ + data: { + permission: { + rbac: { + 'policies-csv-file': csvPermFile, + policyFileReload: true, + }, }, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, }, }); @@ -1085,7 +1088,7 @@ describe('Policy checks for resourced permissions defined by name', () => { let policy: RBACPermissionPolicy; beforeEach(async () => { - const config = newConfigReader(); + const config = newConfig(); const adapter = await newAdapter(config); enfDelegate = await newEnforcerDelegate(adapter, config); policy = await newPermissionPolicy( @@ -1306,7 +1309,7 @@ describe('Policy checks for users and groups', () => { __dirname, './../__fixtures__/data/valid-csv/policy-checks.csv', ); - const config = newConfigReader(policyChecksCSV); + const config = newConfig(policyChecksCSV); const adapter = await newAdapter(config); const enfDelegate = await newEnforcerDelegate(adapter, config); @@ -1789,9 +1792,9 @@ describe('Policy checks for conditional policies', () => { g, group:default/qa, role:default/qa `, ); - const config = newConfigReader(undefined, []); + const config = newConfig(undefined, []); const theModel = newModelFromString(MODEL); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const enf = await createEnforcer(theModel, adapter, logger, config); const enfDelegate = new EnforcerDelegate( @@ -2132,11 +2135,11 @@ function newPolicyQueryUser( return undefined; } -function newConfigReader( +function newConfig( permFile?: string, users?: Array<{ name: string }>, superUsers?: Array<{ name: string }>, -): ConfigReader { +): Config { const testUsers = [ { name: 'user:default/guest', @@ -2146,28 +2149,30 @@ function newConfigReader( }, ]; - return new ConfigReader({ - permission: { - rbac: { - 'policies-csv-file': permFile || csvPermFile, - policyFileReload: true, - admin: { - users: users || testUsers, - superUsers: superUsers, + return mockServices.rootConfig({ + data: { + permission: { + rbac: { + 'policies-csv-file': permFile || csvPermFile, + policyFileReload: true, + admin: { + users: users || testUsers, + superUsers: superUsers, + }, }, }, - }, - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, }, }); } async function newAdapter( - config: ConfigReader, + config: Config, stringPolicy?: string, ): Promise { if (stringPolicy) { @@ -2183,7 +2188,7 @@ async function createEnforcer( theModel: Model, adapter: Adapter, logger: LoggerService, - config: ConfigReader, + config: Config, ): Promise { const catalogDBClient = Knex.knex({ client: MockClient }); const rbacDBClient = Knex.knex({ client: MockClient }); @@ -2206,12 +2211,12 @@ async function createEnforcer( async function newEnforcerDelegate( adapter: Adapter, - config: ConfigReader, + config: Config, storedPolicies?: string[][], storedGroupingPolicies?: string[][], ): Promise { const theModel = newModelFromString(MODEL); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const enf = await createEnforcer(theModel, adapter, logger, config); @@ -2227,11 +2232,11 @@ async function newEnforcerDelegate( } async function newPermissionPolicy( - config: ConfigReader, + config: Config, enfDelegate: EnforcerDelegate, roleMock?: RoleMetadataStorage, ): Promise { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const permissionPolicy = await RBACPermissionPolicy.build( logger, auditLoggerMock, diff --git a/plugins/rbac-backend/src/service/permission-policy.ts b/plugins/rbac-backend/src/service/permission-policy.ts index ca0abb67ea..228cf0c64b 100644 --- a/plugins/rbac-backend/src/service/permission-policy.ts +++ b/plugins/rbac-backend/src/service/permission-policy.ts @@ -1,10 +1,10 @@ -import { +import type { AuthService, BackstageUserInfo, LoggerService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { ConfigApi } from '@backstage/core-plugin-api'; +import type { Config } from '@backstage/config'; +import type { ConfigApi } from '@backstage/core-plugin-api'; import { AuthorizeResult, ConditionalPolicyDecision, @@ -16,15 +16,15 @@ import { PolicyDecision, ResourcePermission, } from '@backstage/plugin-permission-common'; -import { +import type { PermissionPolicy, PolicyQuery, PolicyQueryUser, } from '@backstage/plugin-permission-node'; -import { Knex } from 'knex'; +import type { Knex } from 'knex'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; import { NonEmptyArray, toPermissionAction, diff --git a/plugins/rbac-backend/src/service/plugin-endpoint.test.ts b/plugins/rbac-backend/src/service/plugin-endpoint.test.ts index 4e592cfa2d..5d00f1a551 100644 --- a/plugins/rbac-backend/src/service/plugin-endpoint.test.ts +++ b/plugins/rbac-backend/src/service/plugin-endpoint.test.ts @@ -1,6 +1,5 @@ -import { getVoidLogger, ReadUrlResponse } from '@backstage/backend-common'; +import type { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import { PluginPermissionMetadataCollector } from './plugin-endpoints'; @@ -11,51 +10,11 @@ const backendPluginIDsProviderMock = { }), }; -const config = new ConfigReader({}); - -const logger = getVoidLogger(); - -const mockUrlReaderService = { - readUrl: jest.fn().mockImplementation(() => {}), - readTree: jest.fn().mockImplementation(() => {}), - search: jest.fn().mockImplementation(async () => { - return Promise.resolve({ - files: [], - etag: '', - }); - }), -}; - -const mockAuth = mockServices.auth(); - -jest.mock('@backstage/backend-common', () => { - const actualBackendCommon = jest.requireActual('@backstage/backend-common'); - actualBackendCommon.UrlReaders = { - default: jest.fn(() => mockUrlReaderService), - }; - return actualBackendCommon; -}); - describe('plugin-endpoint', () => { - const mockPluginEndpointDiscovery = { - getBaseUrl: jest.fn().mockImplementation(async (pluginId: string) => { + const mockPluginEndpointDiscovery = mockServices.discovery.mock({ + getBaseUrl: async (pluginId: string) => { return `https://localhost:7007/api/${pluginId}`; - }), - getExternalBaseUrl: jest.fn().mockImplementation(), - }; - - const bufferMock = { - toString: jest.fn().mockImplementation(), - }; - - const mockReadUrlResponse: ReadUrlResponse = { - buffer: jest.fn().mockImplementation(async () => { - return Promise.resolve(bufferMock as any as Buffer); - }), - }; - - beforeEach(() => { - (mockUrlReaderService.readUrl as jest.Mock).mockReset(); + }, }); describe('Test list plugin policies', () => { @@ -63,10 +22,12 @@ describe('plugin-endpoint', () => { const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + ); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); expect(policiesMetadata.length).toEqual(0); }); @@ -74,18 +35,28 @@ describe('plugin-endpoint', () => { it('should return non empty plugin policies list with resourced permission', async () => { backendPluginIDsProviderMock.getPluginIds.mockReturnValue(['permission']); - mockUrlReaderService.readUrl.mockReturnValue(mockReadUrlResponse); - bufferMock.toString.mockReturnValueOnce( - '{"permissions":[{"type":"resource","name":"policy.entity.read","attributes":{"action":"read"},"resourceType":"policy-entity"}]}', - ); + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async () => { + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"resource","name":"policy.entity.read","attributes":{"action":"read"},"resourceType":"policy-entity"}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; + }, + }); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + mockUrlReaderService, + ); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); expect(policiesMetadata.length).toEqual(1); expect(policiesMetadata[0].pluginId).toEqual('permission'); @@ -101,18 +72,28 @@ describe('plugin-endpoint', () => { it('should return non empty plugin policies list with non resourced permission', async () => { backendPluginIDsProviderMock.getPluginIds.mockReturnValue(['permission']); - mockUrlReaderService.readUrl.mockReturnValue(mockReadUrlResponse); - bufferMock.toString.mockReturnValueOnce( - '{"permissions":[{"type":"basic","name":"catalog.entity.create","attributes":{"action":"create"}}]}', - ); + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async () => { + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"basic","name":"catalog.entity.create","attributes":{"action":"create"}}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; + }, + }); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + mockUrlReaderService, + ); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); expect(policiesMetadata.length).toEqual(1); expect(policiesMetadata[0].pluginId).toEqual('permission'); @@ -130,29 +111,36 @@ describe('plugin-endpoint', () => { 'unknown-plugin-id', ]); - mockUrlReaderService.readUrl = jest - .fn() - .mockImplementation(async (wellKnownURL: string) => { + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async (wellKnownURL: string) => { if ( wellKnownURL === 'https://localhost:7007/api/permission/.well-known/backstage/permissions/metadata' ) { - return mockReadUrlResponse; + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; } throw new NotFoundError(); - }); - bufferMock.toString.mockReturnValueOnce( - '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', - ); + }, + }); + const logger = mockServices.logger.mock(); const errorSpy = jest.spyOn(logger, 'warn').mockClear(); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, logger, - config, + mockServices.rootConfig(), + mockUrlReaderService, + ); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); expect(policiesMetadata.length).toEqual(1); expect(policiesMetadata[0].pluginId).toEqual('permission'); @@ -175,30 +163,37 @@ describe('plugin-endpoint', () => { 'catalog', ]); - mockUrlReaderService.readUrl = jest - .fn() - .mockImplementation(async (wellKnownURL: string) => { + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async (wellKnownURL: string) => { if ( wellKnownURL === 'https://localhost:7007/api/permission/.well-known/backstage/permissions/metadata' ) { - return mockReadUrlResponse; + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; } throw new Error('Unexpected error'); - }); - bufferMock.toString.mockReturnValueOnce( - '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', - ); + }, + }); + const logger = mockServices.logger.mock(); const errorSpy = jest.spyOn(logger, 'error').mockClear(); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, logger, - config, + mockServices.rootConfig(), + mockUrlReaderService, ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), + ); expect(policiesMetadata.length).toEqual(1); expect(policiesMetadata[0].pluginId).toEqual('permission'); @@ -221,26 +216,47 @@ describe('plugin-endpoint', () => { 'catalog', ]); - mockUrlReaderService.readUrl = jest - .fn() - .mockImplementation(async (_wellKnownURL: string) => { - return mockReadUrlResponse; - }); - bufferMock.toString - .mockReturnValueOnce( - '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', - ) - .mockReturnValueOnce('non json data'); + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async (wellKnownURL: string) => { + if ( + wellKnownURL === + 'https://localhost:7007/api/permission/.well-known/backstage/permissions/metadata' + ) { + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"resource","resourceType":"policy-entity","name":"policy.entity.read","attributes":{"action":"read"}}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; + } else if ( + wellKnownURL === + 'https://localhost:7007/api/catalog/.well-known/backstage/permissions/metadata' + ) { + return { + buffer: async () => { + return Buffer.from('non json data'); + }, + } as UrlReaderServiceReadUrlResponse; + } + throw new Error('Unexpected error'); + }, + }); - const errorSpy = jest.spyOn(logger, 'error').mockClear(); + const errorSpy = jest + .spyOn(mockServices.logger.mock(), 'error') + .mockClear(); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + mockUrlReaderService, + ); + const policiesMetadata = await collector.getPluginPolicies( + mockServices.auth(), ); - const policiesMetadata = await collector.getPluginPolicies(mockAuth); expect(policiesMetadata.length).toEqual(1); expect(policiesMetadata[0].pluginId).toEqual('permission'); @@ -264,11 +280,12 @@ describe('plugin-endpoint', () => { const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + ); + const conditionRulesMetadata = await collector.getPluginConditionRules( + mockServices.auth(), ); - const conditionRulesMetadata = - await collector.getPluginConditionRules(mockAuth); expect(conditionRulesMetadata.length).toEqual(0); }); @@ -276,19 +293,28 @@ describe('plugin-endpoint', () => { it('should return non empty condition rule list', async () => { backendPluginIDsProviderMock.getPluginIds.mockReturnValue(['catalog']); - mockUrlReaderService.readUrl.mockReturnValue(mockReadUrlResponse); - bufferMock.toString.mockReturnValueOnce( - '{"rules": [{"description":"Allow entities with the specified label","name":"HAS_LABEL","paramsSchema":{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"label":{"description":"Name of the label to match on","type":"string"}},"required":["label"],"type":"object"},"resourceType":"catalog-entity"}]}', - ); + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async () => { + return { + buffer: async () => { + return Buffer.from( + '{"rules": [{"description":"Allow entities with the specified label","name":"HAS_LABEL","paramsSchema":{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"label":{"description":"Name of the label to match on","type":"string"}},"required":["label"],"type":"object"},"resourceType":"catalog-entity"}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; + }, + }); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + mockUrlReaderService, + ); + const conditionRulesMetadata = await collector.getPluginConditionRules( + mockServices.auth(), ); - const conditionRulesMetadata = - await collector.getPluginConditionRules(mockAuth); expect(conditionRulesMetadata.length).toEqual(1); expect(conditionRulesMetadata[0].pluginId).toEqual('catalog'); @@ -318,16 +344,24 @@ describe('plugin-endpoint', () => { it('should return metadata by id', async () => { backendPluginIDsProviderMock.getPluginIds.mockReturnValue(['catalog']); - mockUrlReaderService.readUrl.mockReturnValue(mockReadUrlResponse); - bufferMock.toString.mockReturnValueOnce( - '{"permissions":[{"type":"resource","name":"catalog.entity.read","attributes":{"action":"read"},"resourceType":"catalog-entity"}], "rules": [{"description":"Allow entities with the specified label","name":"HAS_LABEL","paramsSchema":{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"label":{"description":"Name of the label to match on","type":"string"}},"required":["label"],"type":"object"},"resourceType":"catalog-entity"}]}', - ); + const mockUrlReaderService = mockServices.urlReader.mock({ + readUrl: async () => { + return { + buffer: async () => { + return Buffer.from( + '{"permissions":[{"type":"resource","name":"catalog.entity.read","attributes":{"action":"read"},"resourceType":"catalog-entity"}], "rules": [{"description":"Allow entities with the specified label","name":"HAS_LABEL","paramsSchema":{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"label":{"description":"Name of the label to match on","type":"string"}},"required":["label"],"type":"object"},"resourceType":"catalog-entity"}]}', + ); + }, + } as UrlReaderServiceReadUrlResponse; + }, + }); const collector = new PluginPermissionMetadataCollector( mockPluginEndpointDiscovery, backendPluginIDsProviderMock, - logger, - config, + mockServices.logger.mock(), + mockServices.rootConfig(), + mockUrlReaderService, ); const metadata = await collector.getMetadataByPluginId( 'catalog', diff --git a/plugins/rbac-backend/src/service/plugin-endpoints.ts b/plugins/rbac-backend/src/service/plugin-endpoints.ts index 060cbeaba7..9726af8415 100644 --- a/plugins/rbac-backend/src/service/plugin-endpoints.ts +++ b/plugins/rbac-backend/src/service/plugin-endpoints.ts @@ -1,30 +1,30 @@ import { FetchUrlReader, - PluginEndpointDiscovery, ReaderFactory, UrlReaders, -} from '@backstage/backend-common'; -import { +} from '@backstage/backend-defaults/urlReader'; +import type { AuthService, + DiscoveryService, LoggerService, UrlReaderService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { isError } from '@backstage/errors'; import { isResourcePermission, Permission, } from '@backstage/plugin-permission-common'; -import { +import type { MetadataResponse, MetadataResponseSerializedRule, } from '@backstage/plugin-permission-node'; -import { +import type { PluginPermissionMetaData, PolicyDetails, } from '@janus-idp/backstage-plugin-rbac-common'; -import { PluginIdProvider } from '@janus-idp/backstage-plugin-rbac-node'; +import type { PluginIdProvider } from '@janus-idp/backstage-plugin-rbac-node'; type PluginMetadataResponse = { pluginId: string; @@ -38,20 +38,23 @@ export type PluginMetadataResponseSerializedRule = { export class PluginPermissionMetadataCollector { private readonly pluginIds: string[]; - private urlReader: UrlReaderService; + private readonly urlReader: UrlReaderService; constructor( - private readonly discovery: PluginEndpointDiscovery, + private readonly discovery: DiscoveryService, private readonly pluginIdProvider: PluginIdProvider, private readonly logger: LoggerService, config: Config, + urlReader?: UrlReaderService, ) { this.pluginIds = this.pluginIdProvider.getPluginIds(); - this.urlReader = UrlReaders.default({ - config, - logger, - factories: [PluginPermissionMetadataCollector.permissionFactory], - }); + this.urlReader = + urlReader ?? + UrlReaders.default({ + config, + logger, + factories: [PluginPermissionMetadataCollector.permissionFactory], + }); } async getPluginConditionRules( diff --git a/plugins/rbac-backend/src/service/policies-rest-api.test.ts b/plugins/rbac-backend/src/service/policies-rest-api.test.ts index 13cd398e8c..c665a326b5 100644 --- a/plugins/rbac-backend/src/service/policies-rest-api.test.ts +++ b/plugins/rbac-backend/src/service/policies-rest-api.test.ts @@ -1,10 +1,9 @@ -import { errorHandler, getVoidLogger } from '@backstage/backend-common'; +import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { RouterOptions } from '@backstage/plugin-permission-backend'; +import type { RouterOptions } from '@backstage/plugin-permission-backend'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { MetadataResponse } from '@backstage/plugin-permission-node'; +import type { MetadataResponse } from '@backstage/plugin-permission-node'; import express from 'express'; import * as Knex from 'knex'; @@ -23,7 +22,7 @@ import { RoleConditionalPolicyDecision, Source, } from '@janus-idp/backstage-plugin-rbac-common'; -import { RBACProvider } from '@janus-idp/backstage-plugin-rbac-node'; +import type { RBACProvider } from '@janus-idp/backstage-plugin-rbac-node'; import { RoleMetadataDao, @@ -195,23 +194,22 @@ describe('REST policies api', () => { })), }; - const logger = getVoidLogger(); - const mockDiscovery = { - getBaseUrl: jest.fn().mockImplementation(), - getExternalBaseUrl: jest.fn().mockImplementation(), - }; + const logger = mockServices.logger.mock(); + const mockDiscovery = mockServices.discovery.mock(); const knex = Knex.knex({ client: MockClient }); - let config = new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + let config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + enabled: true, }, - }, - permission: { - enabled: true, }, }); @@ -306,7 +304,7 @@ describe('REST policies api', () => { ); const router = await server.serve(); app = express().use(router); - app.use(errorHandler()); + app.use(MiddlewareFactory.create({ logger, config }).error()); conditionalStorage.getCondition.mockReset(); validateRoleConditionMock.mockReset(); auditLoggerMock.auditLog.mockClear(); @@ -3513,7 +3511,7 @@ describe('REST policies api', () => { ); const router = await server.serve(); appWithProvider = express().use(router); - appWithProvider.use(errorHandler()); + appWithProvider.use(MiddlewareFactory.create({ logger, config }).error()); }); it('should return a status of Unauthorized', async () => { @@ -3678,15 +3676,17 @@ describe('REST policies api', () => { describe('test rest API when permission framework disabled', () => { beforeAll(() => { - config = new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config = mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + enabled: false, }, - }, - permission: { - enabled: false, }, }); }); diff --git a/plugins/rbac-backend/src/service/policies-rest-api.ts b/plugins/rbac-backend/src/service/policies-rest-api.ts index ab56d7da91..681bab856a 100644 --- a/plugins/rbac-backend/src/service/policies-rest-api.ts +++ b/plugins/rbac-backend/src/service/policies-rest-api.ts @@ -1,16 +1,16 @@ -import { +import type { AuthService, HttpAuthService, PermissionsService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; import { ConflictError, InputError, NotAllowedError, NotFoundError, } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; +import type { IdentityApi } from '@backstage/plugin-auth-node'; import { createRouter, RouterOptions, @@ -23,11 +23,11 @@ import { import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import express from 'express'; -import { Request } from 'express-serve-static-core'; +import type { Request } from 'express-serve-static-core'; import { isEmpty, isEqual } from 'lodash'; -import { ParsedQs } from 'qs'; +import type { ParsedQs } from 'qs'; -import { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; +import type { AuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; import { PermissionAction, policyEntityCreatePermission, @@ -40,7 +40,7 @@ import { RoleBasedPolicy, RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; -import { RBACProvider } from '@janus-idp/backstage-plugin-rbac-node'; +import type { RBACProvider } from '@janus-idp/backstage-plugin-rbac-node'; import { ConditionAuditInfo, diff --git a/plugins/rbac-backend/src/service/policy-builder.test.ts b/plugins/rbac-backend/src/service/policy-builder.test.ts index 7f85a7f9db..772da77ef2 100644 --- a/plugins/rbac-backend/src/service/policy-builder.test.ts +++ b/plugins/rbac-backend/src/service/policy-builder.test.ts @@ -1,13 +1,12 @@ -import { UserInfoService } from '@backstage/backend-plugin-api'; +import type { UserInfoService } from '@backstage/backend-plugin-api'; import { mockServices } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { Adapter, Enforcer } from 'casbin'; -import { Router } from 'express'; -import TypeORMAdapter from 'typeorm-adapter'; +import type { Adapter, Enforcer } from 'casbin'; +import type { Router } from 'express'; +import type TypeORMAdapter from 'typeorm-adapter'; -import { +import type { PluginIdProvider, RBACProvider, } from '@janus-idp/backstage-plugin-rbac-node'; @@ -129,10 +128,7 @@ describe('PolicyBuilder', () => { })), }; - const mockDiscovery = { - getBaseUrl: jest.fn(), - getExternalBaseUrl: jest.fn(), - }; + const mockDiscovery = mockServices.discovery.mock(); const backendPluginIDsProviderMock = { getPluginIds: jest.fn().mockImplementation(() => { @@ -149,16 +145,18 @@ describe('PolicyBuilder', () => { it('should build policy server', async () => { const router = await PolicyBuilder.build( { - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + enabled: true, + rbac: {}, }, - }, - permission: { - enabled: true, - rbac: {}, }, }), logger, @@ -184,16 +182,18 @@ describe('PolicyBuilder', () => { it('should build policy server with rbac providers', async () => { const router = await PolicyBuilder.build( { - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + enabled: true, + rbac: {}, }, - }, - permission: { - enabled: true, - rbac: {}, }, }), logger, @@ -221,16 +221,18 @@ describe('PolicyBuilder', () => { it('should build policy server, but log warning that permission framework disabled', async () => { const router = await PolicyBuilder.build( { - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + permission: { + enabled: false, + rbac: {}, }, - }, - permission: { - enabled: false, - rbac: {}, }, }), logger, @@ -259,17 +261,19 @@ describe('PolicyBuilder', () => { const pluginIdProvider: PluginIdProvider = { getPluginIds: () => [] }; const router = await PolicyBuilder.build( { - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, - }, - permission: { - enabled: true, - rbac: { - pluginsWithPermission: ['catalog'], + permission: { + enabled: true, + rbac: { + pluginsWithPermission: ['catalog'], + }, }, }, }), @@ -299,17 +303,19 @@ describe('PolicyBuilder', () => { const pluginIdProvider: PluginIdProvider = { getPluginIds: () => ['rbac'] }; const router = await PolicyBuilder.build( { - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, - }, - permission: { - enabled: true, - rbac: { - pluginsWithPermission: ['catalog'], + permission: { + enabled: true, + rbac: { + pluginsWithPermission: ['catalog'], + }, }, }, }), @@ -337,17 +343,19 @@ describe('PolicyBuilder', () => { it('should get list plugin ids from application configuration, but provider should be created by default', async () => { const router = await PolicyBuilder.build({ - config: new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + config: mockServices.rootConfig({ + data: { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, }, - }, - permission: { - enabled: true, - rbac: { - pluginsWithPermission: ['catalog'], + permission: { + enabled: true, + rbac: { + pluginsWithPermission: ['catalog'], + }, }, }, }), diff --git a/plugins/rbac-backend/src/service/policy-builder.ts b/plugins/rbac-backend/src/service/policy-builder.ts index 7acf5806af..f6392f5bc4 100644 --- a/plugins/rbac-backend/src/service/policy-builder.ts +++ b/plugins/rbac-backend/src/service/policy-builder.ts @@ -1,25 +1,23 @@ -import { - createLegacyAuthAdapters, - PluginEndpointDiscovery, -} from '@backstage/backend-common'; +import { createLegacyAuthAdapters } from '@backstage/backend-common'; import { DatabaseManager } from '@backstage/backend-defaults/database'; -import { +import type { AuthService, + DiscoveryService, HttpAuthService, LoggerService, UserInfoService, } from '@backstage/backend-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -import { RouterOptions } from '@backstage/plugin-permission-backend'; -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import type { Config } from '@backstage/config'; +import type { IdentityApi } from '@backstage/plugin-auth-node'; +import type { RouterOptions } from '@backstage/plugin-permission-backend'; +import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { newEnforcer, newModelFromString } from 'casbin'; -import { Router } from 'express'; +import type { Router } from 'express'; import { DefaultAuditLogger } from '@janus-idp/backstage-plugin-audit-log-node'; -import { +import type { PluginIdProvider, RBACProvider, } from '@janus-idp/backstage-plugin-rbac-node'; @@ -41,7 +39,7 @@ export class PolicyBuilder { env: { config: Config; logger: LoggerService; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; identity: IdentityApi; permissions: PermissionEvaluator; auth?: AuthService; diff --git a/plugins/rbac-backend/src/service/router.ts b/plugins/rbac-backend/src/service/router.ts index bddf91d8a9..6b1ed106ee 100644 --- a/plugins/rbac-backend/src/service/router.ts +++ b/plugins/rbac-backend/src/service/router.ts @@ -1,6 +1,6 @@ import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { LoggerService } from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; import express from 'express'; diff --git a/plugins/rbac-backend/src/validation/condition-validation.test.ts b/plugins/rbac-backend/src/validation/condition-validation.test.ts index d6b5246519..aeac74ff61 100644 --- a/plugins/rbac-backend/src/validation/condition-validation.test.ts +++ b/plugins/rbac-backend/src/validation/condition-validation.test.ts @@ -1,6 +1,6 @@ import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { +import type { PermissionAction, RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; diff --git a/plugins/rbac-backend/src/validation/condition-validation.ts b/plugins/rbac-backend/src/validation/condition-validation.ts index 6957c86099..131b1931d3 100644 --- a/plugins/rbac-backend/src/validation/condition-validation.ts +++ b/plugins/rbac-backend/src/validation/condition-validation.ts @@ -4,7 +4,7 @@ import { PermissionRuleParams, } from '@backstage/plugin-permission-common'; -import { +import type { PermissionAction, RoleConditionalPolicyDecision, } from '@janus-idp/backstage-plugin-rbac-common'; diff --git a/plugins/rbac-backend/src/validation/policies-validation.test.ts b/plugins/rbac-backend/src/validation/policies-validation.test.ts index f807de860a..0396b1bd12 100644 --- a/plugins/rbac-backend/src/validation/policies-validation.test.ts +++ b/plugins/rbac-backend/src/validation/policies-validation.test.ts @@ -1,6 +1,6 @@ import Knex from 'knex'; -import { +import type { RoleBasedPolicy, Source, } from '@janus-idp/backstage-plugin-rbac-common';