From 45df287eec039e84722ae838119408d625dbc465 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 7 Jan 2022 15:41:45 +0100 Subject: [PATCH 01/16] Show how to hide locations from search Signed-off-by: Eric Peterson --- docs/features/search/how-to-guides.md | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 4ba44b8ef4..a3c42c682c 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -97,3 +97,37 @@ of the `SearchType` component. ... ``` + +## How to limit what can be searched in the Software Catalog + +The Software Catalog includes a wealth of information about the components, +systems, groups, users, and other aspects of your software ecosystem. However, +you may not always want _every_ aspect to appear when a user searches the +catalog. Examples include: + +- Entities of kind `Location`, which are often not useful to Backstage users. +- Entities of kind `User` or `Group`, if you'd prefer that users and groups be + exposed to search in a different way (or not at all). + +It's possible to write your own [Collator](./concepts.md#collators) to control +exactly what's available to search, (or a [Decorator](./concepts.md#decorators) +to filter things out here and there), but the `DefaultCatalogCollator` that's +provided by `@backstage/plugin-catalog-backend` offers some configuration too! + +```diff +// packages/backend/src/plugins/search.ts + +indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + collator: DefaultCatalogCollator.fromConfig(config, { + discovery, + tokenManager, ++ filter: { ++ kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'], ++ }, + }), +}); +``` + +As shown above, you can add a catalog entity filter to narrow down what catalog +entities are indexed by the search engine. From cd529c409445c124fc3030f5250ba6db957d336c Mon Sep 17 00:00:00 2001 From: Joon Park Date: Fri, 7 Jan 2022 15:49:26 +0000 Subject: [PATCH 02/16] Integrate permissions with catalog-backend refresh endpoint (#8693) Integration permission framework with refresh in catalog-backend ... through the use of a new AuthorizedRefreshService. Signed-off-by: Joon Park --- .changeset/plenty-eyes-brush.md | 60 ++++++++++++++++ .changeset/three-sheep-sparkle.md | 7 ++ packages/create-app/package.json | 2 + packages/create-app/src/lib/versions.ts | 4 ++ .../packages/backend/package.json.hbs | 2 + .../default-app/packages/backend/src/index.ts | 6 ++ .../default-app/packages/backend/src/types.ts | 2 + plugins/catalog-backend/api-report.md | 3 + plugins/catalog-backend/package.json | 3 +- .../src/legacy/service/CatalogBuilder.test.ts | 24 ++++++- .../service/AuthorizedRefreshService.test.ts | 72 +++++++++++++++++++ .../src/service/AuthorizedRefreshService.ts | 47 ++++++++++++ .../src/service/NextCatalogBuilder.ts | 12 ++-- .../src/service/NextRouter.test.ts | 2 + .../catalog-backend/src/service/NextRouter.ts | 16 ++++- .../src/service/standaloneServer.ts | 10 +++ plugins/catalog-backend/src/service/types.ts | 1 + 17 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 .changeset/plenty-eyes-brush.md create mode 100644 .changeset/three-sheep-sparkle.md create mode 100644 plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts create mode 100644 plugins/catalog-backend/src/service/AuthorizedRefreshService.ts diff --git a/.changeset/plenty-eyes-brush.md b/.changeset/plenty-eyes-brush.md new file mode 100644 index 0000000000..354c216c3c --- /dev/null +++ b/.changeset/plenty-eyes-brush.md @@ -0,0 +1,60 @@ +--- +'@backstage/create-app': patch +--- + +Add permissions to create-app's PluginEnvironment + +`CatalogEnvironment` now has a `permissions` field, which means that a permission client must now be provided as part of `PluginEnvironment`. To apply these changes to an existing app, add the following to the `makeCreateEnv` function in `packages/backend/src/index.ts`: + +```diff + // packages/backend/src/index.ts + ++ import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + + function makeCreateEnv(config: Config) { + ... ++ const permissions = ServerPerimssionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + + root.info(`Created UrlReader ${reader}`); + + return (plugin: string): PluginEnvironment => { + ... + return { + logger, + cache, + database, + config, + reader, + discovery, + tokenManager, + scheduler, ++ permissions, + }; + } + } +``` + +And add a permissions field to the `PluginEnvironment` type in `packages/backend/src/types.ts`: + +```diff + // packages/backend/src/types.ts + ++ import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; + + export type PluginEnvironment = { + ... ++ permissions: PermissionAuthorizer; + }; +``` + +[`@backstage/plugin-permission-common`](https://www.npmjs.com/package/@backstage/plugin-permission-common) and [`@backstage/plugin-permission-node`](https://www.npmjs.com/package/@backstage/plugin-permission-node) will need to be installed as dependencies: + +```diff + // packages/backend/package.json + ++ "@backstage/plugin-permission-common": "...", ++ "@backstage/plugin-permission-node": "...", +``` diff --git a/.changeset/three-sheep-sparkle.md b/.changeset/three-sheep-sparkle.md new file mode 100644 index 0000000000..db6d8148ce --- /dev/null +++ b/.changeset/three-sheep-sparkle.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +In order to integrate the permissions system with the refresh endpoint in catalog-backend, a new AuthorizedRefreshService was created as a thin wrapper around the existing refresh service which performs authorization and handles the case when authorization is denied. In order to instantiate AuthorizedRefreshService, a permission client is required, which was added as a new field to `CatalogEnvironment`. + +The new `permissions` field in `CatalogEnvironment` should already receive the permission client from the `PluginEnvrionment`, so there should be no changes required to the catalog backend setup. See [the create-app changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md) for more details. diff --git a/packages/create-app/package.json b/packages/create-app/package.json index e95dbb3a9d..639e0370d7 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -67,6 +67,8 @@ "@backstage/plugin-explore": "*", "@backstage/plugin-github-actions": "*", "@backstage/plugin-lighthouse": "*", + "@backstage/plugin-permission-common": "*", + "@backstage/plugin-permission-node": "*", "@backstage/plugin-proxy-backend": "*", "@backstage/plugin-rollbar-backend": "*", "@backstage/plugin-scaffolder": "*", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 3765422923..f89ff7f391 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -57,6 +57,8 @@ import { version as pluginExplore } from '../../../../plugins/explore/package.js import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json'; import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json'; import { version as pluginOrg } from '../../../../plugins/org/package.json'; +import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json'; +import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json'; import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json'; import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json'; import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json'; @@ -94,6 +96,8 @@ export const packageVersions = { '@backstage/plugin-github-actions': pluginGithubActions, '@backstage/plugin-lighthouse': pluginLighthouse, '@backstage/plugin-org': pluginOrg, + '@backstage/plugin-permission-common': pluginPermissionCommon, + '@backstage/plugin-permission-node': pluginPermissionNode, '@backstage/plugin-proxy-backend': pluginProxyBackend, '@backstage/plugin-rollbar-backend': pluginRollbarBackend, '@backstage/plugin-scaffolder': pluginScaffolder, diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index a2ee89c546..96dc724a1e 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -23,6 +23,8 @@ "@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}", "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", + "@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}", + "@backstage/plugin-permission-node": "^{{version '@backstage/plugin-permission-node'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 08d21e61f7..70bc66bcdd 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -29,6 +29,7 @@ import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; import search from './plugins/search'; import { PluginEnvironment } from './types'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -38,6 +39,10 @@ function makeCreateEnv(config: Config) { const databaseManager = DatabaseManager.fromConfig(config); const tokenManager = ServerTokenManager.noop(); const taskScheduler = TaskScheduler.fromConfig(config); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); root.info(`Created UrlReader ${reader}`); @@ -55,6 +60,7 @@ function makeCreateEnv(config: Config) { discovery, tokenManager, scheduler, + permissions, }; }; } diff --git a/packages/create-app/templates/default-app/packages/backend/src/types.ts b/packages/create-app/templates/default-app/packages/backend/src/types.ts index c3d0158dc6..0862b0e874 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/types.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/types.ts @@ -8,6 +8,7 @@ import { UrlReader, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; export type PluginEnvironment = { logger: Logger; @@ -18,4 +19,5 @@ export type PluginEnvironment = { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; scheduler: PluginTaskScheduler; + permissions: PermissionAuthorizer; }; diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 8536dafe76..6d7834001a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -25,6 +25,7 @@ import { Location as Location_2 } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/catalog-model'; import { Logger as Logger_2 } from 'winston'; import { Organizations } from 'aws-sdk'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -302,6 +303,7 @@ export type CatalogEnvironment = { database: PluginDatabaseManager; config: Config; reader: UrlReader; + permissions: PermissionAuthorizer; }; // @public @@ -1493,6 +1495,7 @@ export type RefreshIntervalFunction = () => number; // @public export type RefreshOptions = { entityRef: string; + authorizationToken?: string; }; // @public diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8472c33369..504e86a736 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -32,11 +32,12 @@ "dependencies": { "@backstage/backend-common": "^0.10.1", "@backstage/catalog-client": "^0.5.3", - "@backstage/plugin-catalog-common": "^0.1.0", "@backstage/catalog-model": "^0.9.8", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.5", "@backstage/integration": "^0.7.0", + "@backstage/plugin-catalog-common": "^0.1.0", + "@backstage/plugin-permission-common": "^0.3.0", "@backstage/plugin-permission-node": "^0.2.3", "@backstage/search-common": "^0.2.1", "@backstage/types": "^0.1.1", diff --git a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts index 926aa67635..728d35ea3d 100644 --- a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { getVoidLogger, UrlReader } from '@backstage/backend-common'; +import { + getVoidLogger, + PluginEndpointDiscovery, + ServerTokenManager, + UrlReader, +} from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { Knex } from 'knex'; @@ -24,6 +29,7 @@ import { CatalogProcessorParser } from '../../ingestion'; import * as result from '../../ingestion/processors/results'; import { CatalogBuilder } from './CatalogBuilder'; import { CatalogEnvironment } from '../../service'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; const dummyEntity = { apiVersion: 'backstage.io/v1alpha1', @@ -47,11 +53,25 @@ describe('CatalogBuilder', () => { readTree: jest.fn(), search: jest.fn(), }; + const config = new ConfigReader({}); + const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base'; + const discovery: PluginEndpointDiscovery = { + async getBaseUrl() { + return mockBaseUrl; + }, + async getExternalBaseUrl() { + return mockBaseUrl; + }, + }; const env: CatalogEnvironment = { logger: getVoidLogger(), database: { getClient: async () => db }, - config: new ConfigReader({}), + config, reader, + permissions: ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: ServerTokenManager.noop(), + }), }; beforeEach(async () => { diff --git a/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts b/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts new file mode 100644 index 0000000000..82bedec573 --- /dev/null +++ b/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NotAllowedError } from '@backstage/errors'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { AuthorizedRefreshService } from './AuthorizedRefreshService'; + +describe('AuthorizedRefreshService', () => { + const refreshService = { + refresh: jest.fn(), + }; + const permissionApi = { + authorize: jest.fn(), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('throws AuthorizationError on deny', async () => { + permissionApi.authorize.mockResolvedValueOnce([ + { + result: AuthorizeResult.DENY, + }, + ]); + const authorizedService = new AuthorizedRefreshService( + refreshService, + permissionApi as unknown as ServerPermissionClient, + ); + + await expect(() => + authorizedService.refresh({ + entityRef: 'some entity ref', + authorizationToken: 'some auth token', + }), + ).rejects.toThrowError(NotAllowedError); + }); + + it('calls refresh on allow', async () => { + permissionApi.authorize.mockResolvedValueOnce([ + { + result: AuthorizeResult.ALLOW, + }, + ]); + const authorizedService = new AuthorizedRefreshService( + refreshService, + permissionApi as unknown as ServerPermissionClient, + ); + + const options = { + entityRef: 'some entity ref', + authorizationToken: 'some auth token', + }; + await authorizedService.refresh(options); + + expect(refreshService.refresh).toHaveBeenCalledWith(options); + }); +}); diff --git a/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts b/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts new file mode 100644 index 0000000000..800bdcf1b9 --- /dev/null +++ b/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NotAllowedError } from '@backstage/errors'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common'; +import { + AuthorizeResult, + PermissionAuthorizer, +} from '@backstage/plugin-permission-common'; +import { RefreshOptions, RefreshService } from './types'; + +export class AuthorizedRefreshService implements RefreshService { + constructor( + private readonly service: RefreshService, + private readonly permissionApi: PermissionAuthorizer, + ) {} + + async refresh(options: RefreshOptions) { + const authorizeResponse = ( + await this.permissionApi.authorize( + [ + { + permission: catalogEntityRefreshPermission, + resourceRef: options.entityRef, + }, + ], + { token: options.authorizationToken }, + ) + )[0]; + if (authorizeResponse.result !== AuthorizeResult.ALLOW) { + throw new NotAllowedError(); + } + await this.service.refresh(options); + } +} diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index d0c01c3e71..1dcb632e64 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -77,6 +77,7 @@ import { } from '../processing/refresh'; import { createNextRouter } from './NextRouter'; import { DefaultRefreshService } from './DefaultRefreshService'; +import { AuthorizedRefreshService } from './AuthorizedRefreshService'; import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; @@ -84,12 +85,14 @@ import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; import { CatalogPermissionRule } from '../permissions/types'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; export type CatalogEnvironment = { logger: Logger; database: PluginDatabaseManager; config: Config; reader: UrlReader; + permissions: PermissionAuthorizer; }; /** @@ -344,7 +347,7 @@ export class NextCatalogBuilder { locationService: LocationService; router: Router; }> { - const { config, database, logger } = this.env; + const { config, database, logger, permissions } = this.env; const policy = this.buildEntityPolicy(); const processors = this.buildProcessors(); @@ -398,9 +401,10 @@ export class NextCatalogBuilder { locationStore, orchestrator, ); - const refreshService = new DefaultRefreshService({ - database: processingDatabase, - }); + const refreshService = new AuthorizedRefreshService( + new DefaultRefreshService({ database: processingDatabase }), + permissions, + ); const router = await createNextRouter({ entitiesCatalog, locationAnalyzer, diff --git a/plugins/catalog-backend/src/service/NextRouter.test.ts b/plugins/catalog-backend/src/service/NextRouter.test.ts index c31de07718..e835c4620a 100644 --- a/plugins/catalog-backend/src/service/NextRouter.test.ts +++ b/plugins/catalog-backend/src/service/NextRouter.test.ts @@ -66,10 +66,12 @@ describe('createNextRouter readonly disabled', () => { const response = await request(app) .post('/refresh') .set('Content-Type', 'application/json') + .set('authorization', 'Bearer someauthtoken') .send({ entityRef: 'Component/default:foo' }); expect(response.status).toBe(200); expect(refreshService.refresh).toHaveBeenCalledWith({ entityRef: 'Component/default:foo', + authorizationToken: 'someauthtoken', }); }); }); diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 059f937dd7..7aae564a45 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -40,7 +40,7 @@ import { parseEntityTransformParams, } from '../service/request'; import { disallowReadonlyMode, validateRequestBody } from '../service/util'; -import { RefreshService, RefreshOptions, LocationService } from './types'; +import { RefreshOptions, LocationService, RefreshService } from './types'; export interface NextRouterOptions { entitiesCatalog?: EntitiesCatalog; @@ -77,6 +77,10 @@ export async function createNextRouter( if (refreshService) { router.post('/refresh', async (req, res) => { const refreshOptions: RefreshOptions = req.body; + refreshOptions.authorizationToken = getBearerToken( + req.header('authorization'), + ); + await refreshService.refresh(refreshOptions); res.status(200).send(); }); @@ -214,3 +218,13 @@ async function getEntityResource( return entities[0]; } + +function getBearerToken( + authorizationHeader: string | undefined, +): string | undefined { + if (typeof authorizationHeader !== 'string') { + return undefined; + } + const matches = authorizationHeader.match(/Bearer\s+(\S+)/i); + return matches?.[1]; +} diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 7aae3cd47c..8824c3b0d6 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -17,6 +17,8 @@ import { createServiceBuilder, loadBackendConfig, + ServerTokenManager, + SingleHostDiscovery, UrlReaders, useHotMemoize, } from '@backstage/backend-common'; @@ -25,6 +27,7 @@ import { Logger } from 'winston'; import { DatabaseManager } from '../legacy/database'; import { CatalogBuilder } from '../legacy/service/CatalogBuilder'; import { createRouter } from '../legacy/service'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; export interface ServerOptions { port: number; @@ -42,6 +45,12 @@ export async function startStandaloneServer( const db = useHotMemoize(module, () => DatabaseManager.createInMemoryDatabaseConnection(), ); + const discovery = SingleHostDiscovery.fromConfig(config); + const tokenManager = ServerTokenManager.fromConfig(config, { logger }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); logger.debug('Creating application...'); const builder = new CatalogBuilder({ @@ -49,6 +58,7 @@ export async function startStandaloneServer( database: { getClient: () => db }, config, reader, + permissions, }); const { entitiesCatalog, locationsCatalog, higherOrderOperation } = await builder.build(); diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index 05d3d00f4d..5e2bf2a599 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -34,6 +34,7 @@ export interface LocationService { export type RefreshOptions = { /** The reference to a single entity that should be refreshed */ entityRef: string; + authorizationToken?: string; }; /** From f2134e7029f704331370dd7516d85063b4a7be7a Mon Sep 17 00:00:00 2001 From: Daan Boerlage Date: Fri, 7 Jan 2022 17:03:11 +0100 Subject: [PATCH 03/16] docs: Add EF Education First to the list of adopters --- ADOPTERS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index e0e6a818c6..b35f6a3d77 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -77,5 +77,6 @@ | [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon), [Gauthier Roebroeck](https://github.com/gauthier-roebroeck-mox) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | | [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 | | [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. | -| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | -| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | +| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | +| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | +| [EF Education First](https://www.ef.com) | [Daan Boerlage](https://github.com/runebaas), [Rafał Nowosielski](https://github.com/rnowosielski) | Our developer portal - primarily used for cataloging and scaffolding with the ambition to expand with more feature adoptions over time | From 8fe84eadaf5a414240aac2ba7d57141a4a5122a5 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 6 Jan 2022 16:46:17 +0000 Subject: [PATCH 04/16] catalog-backend: add type parameter to CatalogPermissionRule type Previously the CatalogPermissionRule type had a fixed type of unknown[] for the parameters expected in the `apply` and `toQuery` methods. This meant that conditions generated for these rules would always have unknown parameters too, which makes using them in policies much more difficult. To address this, this commit introduces a mandatory type parameter for CatalogPermissionRule which is expected to be set to a tuple corresponding to the expected parameters. Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 23 +++++++++++-------- .../permissions/rules/createPropertyRule.ts | 2 +- .../src/permissions/rules/hasAnnotation.ts | 2 +- .../src/permissions/rules/hasLabel.ts | 2 +- .../src/permissions/rules/isEntityKind.ts | 2 +- .../src/permissions/rules/isEntityOwner.ts | 2 +- .../catalog-backend/src/permissions/types.ts | 5 ++-- .../src/service/NextCatalogBuilder.ts | 4 ++-- .../catalog-backend/src/service/NextRouter.ts | 2 +- 9 files changed, 24 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 6d7834001a..33f0fab18e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -307,9 +307,10 @@ export type CatalogEnvironment = { }; // @public -export type CatalogPermissionRule = PermissionRule< +export type CatalogPermissionRule = PermissionRule< Entity, - EntitiesSearchFilter + EntitiesSearchFilter, + TParams >; // Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1311,7 +1312,9 @@ export class NextCatalogBuilder { addEntityPolicy(...policies: EntityPolicy[]): NextCatalogBuilder; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder; - addPermissionRules(...permissionRules: CatalogPermissionRule[]): void; + addPermissionRules( + ...permissionRules: CatalogPermissionRule[] + ): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder; build(): Promise<{ @@ -1359,7 +1362,7 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) - permissionRules?: CatalogPermissionRule[]; + permissionRules?: CatalogPermissionRule[]; // (undocumented) refreshService?: RefreshService; } @@ -1394,12 +1397,12 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule; - hasLabel: CatalogPermissionRule; - hasMetadata: CatalogPermissionRule; - hasSpec: CatalogPermissionRule; - isEntityKind: CatalogPermissionRule; - isEntityOwner: CatalogPermissionRule; + hasAnnotation: CatalogPermissionRule<[annotation: string]>; + hasLabel: CatalogPermissionRule<[label: string]>; + hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; + hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; + isEntityKind: CatalogPermissionRule<[kinds: string[]]>; + isEntityOwner: CatalogPermissionRule<[claims: string[]]>; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 92e53f930f..254eb164da 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -21,7 +21,7 @@ import { get } from 'lodash'; export function createPropertyRule( propertyType: 'metadata' | 'spec', -): CatalogPermissionRule { +): CatalogPermissionRule<[key: string, value?: string]> { return { name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 57ded3d779..baab28d838 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -23,7 +23,7 @@ import { CatalogPermissionRule } from '../types'; * annotation on a given entity. * @public */ -export const hasAnnotation: CatalogPermissionRule = { +export const hasAnnotation: CatalogPermissionRule<[annotation: string]> = { name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index f93c5aeae6..a790f1441f 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -23,7 +23,7 @@ import { CatalogPermissionRule } from '../types'; * label in its metadata. * @public */ -export const hasLabel: CatalogPermissionRule = { +export const hasLabel: CatalogPermissionRule<[label: string]> = { name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', apply: (resource: Entity, label: string) => diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 64fcf7482d..024188939f 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -22,7 +22,7 @@ import { CatalogPermissionRule } from '../types'; * kind. * @public */ -export const isEntityKind: CatalogPermissionRule = { +export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', apply(resource: Entity, kinds: string[]) { diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index e89159e6b4..e450cdf721 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -27,7 +27,7 @@ import { CatalogPermissionRule } from '../types'; * owner. * @public */ -export const isEntityOwner: CatalogPermissionRule = { +export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', apply: (resource: Entity, claims: string[]) => { diff --git a/plugins/catalog-backend/src/permissions/types.ts b/plugins/catalog-backend/src/permissions/types.ts index cc2b8f4d45..4a0d9dc1fc 100644 --- a/plugins/catalog-backend/src/permissions/types.ts +++ b/plugins/catalog-backend/src/permissions/types.ts @@ -24,7 +24,8 @@ import { EntitiesSearchFilter } from '../catalog/types'; * * @public */ -export type CatalogPermissionRule = PermissionRule< +export type CatalogPermissionRule = PermissionRule< Entity, - EntitiesSearchFilter + EntitiesSearchFilter, + TParams >; diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index 1dcb632e64..e707423670 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -130,7 +130,7 @@ export class NextCatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; - private permissionRules: CatalogPermissionRule[]; + private permissionRules: CatalogPermissionRule[]; constructor(env: CatalogEnvironment) { this.env = env; @@ -331,7 +331,7 @@ export class NextCatalogBuilder { * * @param permissionRules - Additional permission rules */ - addPermissionRules(...permissionRules: CatalogPermissionRule[]) { + addPermissionRules(...permissionRules: CatalogPermissionRule[]) { this.permissionRules.push(...permissionRules); } diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 7aae564a45..948424ec88 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -49,7 +49,7 @@ export interface NextRouterOptions { refreshService?: RefreshService; logger: Logger; config: Config; - permissionRules?: CatalogPermissionRule[]; + permissionRules?: CatalogPermissionRule[]; } export async function createNextRouter( From 9db1b86f3244579c0e5b1f5aced5371aabf08c59 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:26:54 +0000 Subject: [PATCH 05/16] permission-node: add helpers for creating PermissionRules Signed-off-by: MT Lewis --- .changeset/healthy-toes-laugh.md | 5 +++ plugins/permission-node/api-report.md | 16 +++++++ .../src/integration/createPermissionRule.ts | 45 +++++++++++++++++++ .../permission-node/src/integration/index.ts | 1 + 4 files changed, 67 insertions(+) create mode 100644 .changeset/healthy-toes-laugh.md create mode 100644 plugins/permission-node/src/integration/createPermissionRule.ts diff --git a/.changeset/healthy-toes-laugh.md b/.changeset/healthy-toes-laugh.md new file mode 100644 index 0000000000..e0db38e5e3 --- /dev/null +++ b/.changeset/healthy-toes-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': patch +--- + +Add helpers for creating PermissionRules with inferred types diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 25a8be06c8..e743f18311 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -95,6 +95,22 @@ export const createPermissionIntegrationRouter: (options: { getResource: (resourceRef: string) => Promise; }) => Router; +// @public +export const createPermissionRule: < + TResource, + TQuery, + TParams extends unknown[], +>( + rule: PermissionRule, +) => PermissionRule; + +// @public +export const makeCreatePermissionRule: () => < + TParams extends unknown[], +>( + rule: PermissionRule, +) => PermissionRule; + // @public export interface PermissionPolicy { // (undocumented) diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts new file mode 100644 index 0000000000..f3a0519a70 --- /dev/null +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PermissionRule } from '../types'; + +/** + * Helper function to ensure that {@link PermissionRule} definitions are typed correctly. + * + * @public + */ +export const createPermissionRule = < + TResource, + TQuery, + TParams extends unknown[], +>( + rule: PermissionRule, +) => rule; + +/** + * Helper for making plugin-specific createPermissionRule functions, that have + * the TResource and TQuery type parameters populated but infer the params from + * the supplied rule. This helps ensure that rules created for this plugin use + * consistent types for the resource and query. + * + * @public + */ +export const makeCreatePermissionRule = + () => + ( + rule: PermissionRule, + ) => + createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/index.ts b/plugins/permission-node/src/integration/index.ts index f070d57c8f..978342e4ed 100644 --- a/plugins/permission-node/src/integration/index.ts +++ b/plugins/permission-node/src/integration/index.ts @@ -18,3 +18,4 @@ export * from './createConditionFactory'; export * from './createConditionExports'; export * from './createConditionTransformer'; export * from './createPermissionIntegrationRouter'; +export * from './createPermissionRule'; From 82cfdc8d02e5595d89604b698e10c9bdb52dadfb Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:47:14 +0000 Subject: [PATCH 06/16] catalog-backend: use createPermissionRule helper instead of manually typing rules Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 33 +++++++++++++++---- .../permissions/rules/createPropertyRule.ts | 16 ++++----- .../src/permissions/rules/hasAnnotation.ts | 9 +++-- .../src/permissions/rules/hasLabel.ts | 9 +++-- .../src/permissions/rules/index.ts | 2 ++ .../src/permissions/rules/isEntityKind.ts | 6 ++-- .../src/permissions/rules/isEntityOwner.ts | 9 +++-- .../src/permissions/rules/util.ts | 31 +++++++++++++++++ 8 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 plugins/catalog-backend/src/permissions/rules/util.ts diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 33f0fab18e..aafff2a22e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -529,6 +529,11 @@ export class CommonDatabase implements Database { ): Promise; } +// @public +export const createCatalogPermissionRule: ( + rule: PermissionRule, +) => PermissionRule; + // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @deprecated (undocumented) @@ -1397,12 +1402,28 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule<[annotation: string]>; - hasLabel: CatalogPermissionRule<[label: string]>; - hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; - hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; - isEntityKind: CatalogPermissionRule<[kinds: string[]]>; - isEntityOwner: CatalogPermissionRule<[claims: string[]]>; + hasAnnotation: PermissionRule< + Entity, + EntitiesSearchFilter, + [annotation: string] + >; + hasLabel: PermissionRule; + hasMetadata: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + hasSpec: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + isEntityKind: PermissionRule; + isEntityOwner: PermissionRule< + Entity, + EntitiesSearchFilter, + [claims: string[]] + >; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 254eb164da..27a6033362 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -14,15 +14,12 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; import { get } from 'lodash'; +import { Entity } from '@backstage/catalog-model'; +import { createCatalogPermissionRule } from './util'; -export function createPropertyRule( - propertyType: 'metadata' | 'spec', -): CatalogPermissionRule<[key: string, value?: string]> { - return { +export const createPropertyRule = (propertyType: 'metadata' | 'spec') => + createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, apply: (resource: Entity, key: string, value?: string) => { @@ -32,9 +29,8 @@ export function createPropertyRule( } return !!foundValue; }, - toQuery: (key: string, value?: string): EntitiesSearchFilter => ({ + toQuery: (key: string, value?: string) => ({ key: `${propertyType}.${key}`, ...(value !== undefined && { values: [value] }), }), - }; -} + }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index baab28d838..f82ace6580 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -15,21 +15,20 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for the presence of an * annotation on a given entity. * @public */ -export const hasAnnotation: CatalogPermissionRule<[annotation: string]> = { +export const hasAnnotation = createCatalogPermissionRule({ name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', apply: (resource: Entity, annotation: string) => !!resource.metadata.annotations?.hasOwnProperty(annotation), - toQuery: (annotation: string): EntitiesSearchFilter => ({ + toQuery: (annotation: string) => ({ key: `metadata.annotations.${annotation}`, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index a790f1441f..8e5a3341d7 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -15,20 +15,19 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * label in its metadata. * @public */ -export const hasLabel: CatalogPermissionRule<[label: string]> = { +export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', apply: (resource: Entity, label: string) => !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: (label: string): EntitiesSearchFilter => ({ + toQuery: (label: string) => ({ key: `metadata.labels.${label}`, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/index.ts b/plugins/catalog-backend/src/permissions/rules/index.ts index 68d1e38715..4eec796c74 100644 --- a/plugins/catalog-backend/src/permissions/rules/index.ts +++ b/plugins/catalog-backend/src/permissions/rules/index.ts @@ -34,3 +34,5 @@ export const permissionRules = { isEntityKind, isEntityOwner, }; + +export { createCatalogPermissionRule } from './util'; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 024188939f..ddaecd314e 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -15,14 +15,14 @@ */ import { Entity } from '@backstage/catalog-model'; import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * kind. * @public */ -export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { +export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', apply(resource: Entity, kinds: string[]) { @@ -35,4 +35,4 @@ export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), }; }, -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index e450cdf721..a7413c4941 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -19,15 +19,14 @@ import { RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * owner. * @public */ -export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { +export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', apply: (resource: Entity, claims: string[]) => { @@ -39,8 +38,8 @@ export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { .filter(relation => relation.type === RELATION_OWNED_BY) .some(relation => claims.includes(stringifyEntityRef(relation.target))); }, - toQuery: (claims: string[]): EntitiesSearchFilter => ({ + toQuery: (claims: string[]) => ({ key: 'relations.ownedBy', values: claims, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts new file mode 100644 index 0000000000..7ef0ca3546 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '../../catalog/types'; + +/** + * Helper function for creating correctly-typed + * {@link @backstage/plugin-permission-node#PermissionRule}s for the + * catalog-backend. + * + * @public + */ +export const createCatalogPermissionRule = makeCreatePermissionRule< + Entity, + EntitiesSearchFilter +>(); From bef617807bb39973f9d458dce9227e172232e80c Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:50:51 +0000 Subject: [PATCH 07/16] catalog-backend: custom createPermissionRule implementation Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 32 +++++-------------- .../src/permissions/rules/util.ts | 11 +++---- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index aafff2a22e..d310ab001f 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -531,8 +531,8 @@ export class CommonDatabase implements Database { // @public export const createCatalogPermissionRule: ( - rule: PermissionRule, -) => PermissionRule; + rule: CatalogPermissionRule, +) => CatalogPermissionRule; // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1402,28 +1402,12 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: PermissionRule< - Entity, - EntitiesSearchFilter, - [annotation: string] - >; - hasLabel: PermissionRule; - hasMetadata: PermissionRule< - Entity, - EntitiesSearchFilter, - [key: string, value?: string | undefined] - >; - hasSpec: PermissionRule< - Entity, - EntitiesSearchFilter, - [key: string, value?: string | undefined] - >; - isEntityKind: PermissionRule; - isEntityOwner: PermissionRule< - Entity, - EntitiesSearchFilter, - [claims: string[]] - >; + hasAnnotation: CatalogPermissionRule<[annotation: string]>; + hasLabel: CatalogPermissionRule<[label: string]>; + hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; + hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; + isEntityKind: CatalogPermissionRule<[kinds: string[]]>; + isEntityOwner: CatalogPermissionRule<[claims: string[]]>; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 7ef0ca3546..7d17d64e49 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; /** * Helper function for creating correctly-typed @@ -25,7 +23,6 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * * @public */ -export const createCatalogPermissionRule = makeCreatePermissionRule< - Entity, - EntitiesSearchFilter ->(); +export const createCatalogPermissionRule = ( + rule: CatalogPermissionRule, +) => rule; From 10b4ba686f234ef0b301a5204a71fba6f12c0637 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 15:04:50 +0000 Subject: [PATCH 08/16] catalog-backend: remove CatalogPermissionRule type Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 47 ++++++++++++------- .../catalog-backend/src/permissions/index.ts | 1 - .../src/permissions/rules/hasAnnotation.ts | 5 +- .../src/permissions/rules/hasLabel.ts | 4 +- .../src/permissions/rules/hasMetadata.ts | 5 +- .../src/permissions/rules/hasSpec.ts | 5 +- .../src/permissions/rules/isEntityKind.ts | 4 +- .../src/permissions/rules/isEntityOwner.ts | 4 +- .../src/permissions/rules/util.ts | 11 +++-- .../catalog-backend/src/permissions/types.ts | 31 ------------ .../src/service/NextCatalogBuilder.ts | 19 ++++++-- .../catalog-backend/src/service/NextRouter.ts | 10 ++-- 12 files changed, 73 insertions(+), 73 deletions(-) delete mode 100644 plugins/catalog-backend/src/permissions/types.ts diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d310ab001f..4b4036ee8a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -306,13 +306,6 @@ export type CatalogEnvironment = { permissions: PermissionAuthorizer; }; -// @public -export type CatalogPermissionRule = PermissionRule< - Entity, - EntitiesSearchFilter, - TParams ->; - // Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -531,8 +524,8 @@ export class CommonDatabase implements Database { // @public export const createCatalogPermissionRule: ( - rule: CatalogPermissionRule, -) => CatalogPermissionRule; + rule: PermissionRule, +) => PermissionRule; // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1318,7 +1311,11 @@ export class NextCatalogBuilder { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder; addPermissionRules( - ...permissionRules: CatalogPermissionRule[] + ...permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[] ): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder; @@ -1367,7 +1364,7 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) - permissionRules?: CatalogPermissionRule[]; + permissionRules?: PermissionRule[]; // (undocumented) refreshService?: RefreshService; } @@ -1402,12 +1399,28 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule<[annotation: string]>; - hasLabel: CatalogPermissionRule<[label: string]>; - hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; - hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; - isEntityKind: CatalogPermissionRule<[kinds: string[]]>; - isEntityOwner: CatalogPermissionRule<[claims: string[]]>; + hasAnnotation: PermissionRule< + Entity, + EntitiesSearchFilter, + [annotation: string] + >; + hasLabel: PermissionRule; + hasMetadata: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + hasSpec: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + isEntityKind: PermissionRule; + isEntityOwner: PermissionRule< + Entity, + EntitiesSearchFilter, + [claims: string[]] + >; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/index.ts b/plugins/catalog-backend/src/permissions/index.ts index e7ece1dbc7..624d6dd7ac 100644 --- a/plugins/catalog-backend/src/permissions/index.ts +++ b/plugins/catalog-backend/src/permissions/index.ts @@ -15,4 +15,3 @@ */ export * from './rules'; -export type { CatalogPermissionRule } from './types'; diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index f82ace6580..81ebd52789 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -18,8 +18,9 @@ import { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for the presence of an - * annotation on a given entity. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for the presence of an annotation on a given entity. + * * @public */ export const hasAnnotation = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 8e5a3341d7..04b00d68fa 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -18,8 +18,8 @@ import { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * label in its metadata. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified label in its metadata. * @public */ export const hasLabel = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts index fa592feb44..f5f25a5ecf 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts @@ -17,8 +17,9 @@ import { createPropertyRule } from './createPropertyRule'; /** - * A {@link CatalogPermissionRule} which filters for entities with the specified - * metadata subfield. Also matches on values if value is provided. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with the specified metadata subfield. Also matches on + * values if value is provided. * * The key argument to the `apply` and `toQuery` methods can be nested, such as * 'field.nestedfield'. diff --git a/plugins/catalog-backend/src/permissions/rules/hasSpec.ts b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts index 73a7519e1a..891cf1d58c 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasSpec.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts @@ -17,8 +17,9 @@ import { createPropertyRule } from './createPropertyRule'; /** - * A {@link CatalogPermissionRule} which filters for entities with the specified - * spec subfield. Also matches on values if value is provided. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with the specified spec subfield. Also matches on values + * if value is provided. * * The key argument to the `apply` and `toQuery` methods can be nested, such as * 'field.nestedfield'. diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index ddaecd314e..6356c94dc4 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -18,8 +18,8 @@ import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * kind. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified kind. * @public */ export const isEntityKind = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index a7413c4941..3176b31b87 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -22,8 +22,8 @@ import { import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * owner. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified owner. * @public */ export const isEntityOwner = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 7d17d64e49..7ef0ca3546 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import { CatalogPermissionRule } from '../types'; +import { Entity } from '@backstage/catalog-model'; +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '../../catalog/types'; /** * Helper function for creating correctly-typed @@ -23,6 +25,7 @@ import { CatalogPermissionRule } from '../types'; * * @public */ -export const createCatalogPermissionRule = ( - rule: CatalogPermissionRule, -) => rule; +export const createCatalogPermissionRule = makeCreatePermissionRule< + Entity, + EntitiesSearchFilter +>(); diff --git a/plugins/catalog-backend/src/permissions/types.ts b/plugins/catalog-backend/src/permissions/types.ts deleted file mode 100644 index 4a0d9dc1fc..0000000000 --- a/plugins/catalog-backend/src/permissions/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Entity } from '@backstage/catalog-model'; -import { PermissionRule } from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../catalog/types'; - -/** - * A conditional rule that can be used to filter catalog entities for an - * authorization request. See - * {@link @backstage/plugin-permission-node#PermissionRule} for more details. - * - * @public - */ -export type CatalogPermissionRule = PermissionRule< - Entity, - EntitiesSearchFilter, - TParams ->; diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index e707423670..36d4347ede 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -17,6 +17,7 @@ import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, + Entity, EntityPolicies, EntityPolicy, FieldFormatEntityPolicy, @@ -29,7 +30,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { createHash } from 'crypto'; import { Router } from 'express'; import lodash from 'lodash'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog'; import { DatabaseLocationsCatalog, LocationsCatalog, @@ -83,9 +84,9 @@ import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; -import { CatalogPermissionRule } from '../permissions/types'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +import { PermissionRule } from '@backstage/plugin-permission-node'; export type CatalogEnvironment = { logger: Logger; @@ -130,7 +131,11 @@ export class NextCatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; - private permissionRules: CatalogPermissionRule[]; + private permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[]; constructor(env: CatalogEnvironment) { this.env = env; @@ -331,7 +336,13 @@ export class NextCatalogBuilder { * * @param permissionRules - Additional permission rules */ - addPermissionRules(...permissionRules: CatalogPermissionRule[]) { + addPermissionRules( + ...permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[] + ) { this.permissionRules.push(...permissionRules); } diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 948424ec88..5338f58f91 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -25,14 +25,16 @@ import { import { Config } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { + createPermissionIntegrationRouter, + PermissionRule, +} from '@backstage/plugin-permission-node'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import yn from 'yn'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog'; import { LocationAnalyzer } from '../ingestion/types'; -import { CatalogPermissionRule } from '../permissions/types'; import { basicEntityFilter, parseEntityFilterParams, @@ -49,7 +51,7 @@ export interface NextRouterOptions { refreshService?: RefreshService; logger: Logger; config: Config; - permissionRules?: CatalogPermissionRule[]; + permissionRules?: PermissionRule[]; } export async function createNextRouter( From 23046ab18d1b83594c3f6ae88c97adbbb4de1d6b Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Fri, 7 Jan 2022 18:47:31 +0100 Subject: [PATCH 09/16] chore: we're generating api-reports not changesets Signed-off-by: Ben Lambert --- scripts/api-extractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index c7aaede9b0..1a00ef4c5e 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -734,7 +734,7 @@ async function main() { if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) { console.log(''); console.log( - 'TIP: You can generate changesets for select packages by passing package paths:', + 'TIP: You can generate api-reports for select packages by passing package paths:', ); console.log(''); console.log( From a95e7880590ba432b4705c6648a41ba2e626a4de Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 8 Jan 2022 16:22:02 +0100 Subject: [PATCH 10/16] config: move reader test to correct block Signed-off-by: Patrik Oldsberg --- packages/config/src/reader.test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 3cbab17637..2472222c2b 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -265,6 +265,19 @@ describe('ConfigReader', () => { withLogCollector(() => config.getOptionalConfigArray('b')), ).toMatchObject({ warn: [] }); }); + + it('should coerce number strings to numbers', () => { + const config = ConfigReader.fromConfigs([ + { + data: { + port: '123', + }, + context: '1', + }, + ]); + + expect(config.getNumber('port')).toEqual(123); + }); }); describe('ConfigReader with fallback', () => { @@ -660,17 +673,4 @@ describe('ConfigReader.get()', () => { }, }); }); - - it('coerces number strings to numbers', () => { - const config = ConfigReader.fromConfigs([ - { - data: { - port: '123', - }, - context: '1', - }, - ]); - - expect(config.getNumber('port')).toEqual(123); - }); }); From f5343e7c1aea3877df023d53481d49912c458e7e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 8 Jan 2022 16:23:26 +0100 Subject: [PATCH 11/16] config: make get always return a clone Signed-off-by: Patrik Oldsberg --- .changeset/clean-wolves-jog.md | 5 ++++ packages/config/src/reader.test.ts | 47 ++++++++++++++++++++++++++++++ packages/config/src/reader.ts | 9 ++---- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 .changeset/clean-wolves-jog.md diff --git a/.changeset/clean-wolves-jog.md b/.changeset/clean-wolves-jog.md new file mode 100644 index 0000000000..70ab23715f --- /dev/null +++ b/.changeset/clean-wolves-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/config': patch +--- + +The `ConfigReader#get` method now always returns a deep clone of the configuration data. diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 2472222c2b..c0627a200b 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -673,4 +673,51 @@ describe('ConfigReader.get()', () => { }, }); }); + + it('should return deep clones of the backing data', () => { + const data1 = { + foo: { + bar: [], + baz: {}, + }, + }; + const data2 = { + x: { + y: { + z: {}, + }, + }, + }; + + const reader = ConfigReader.fromConfigs([ + { data: data1, context: '1' }, + { data: data2, context: '2' }, + ]); + + reader.get().foo.bar.push(1); + reader.get('foo').bar.push(1); + reader.get('foo.bar').push(1); + reader.get().foo.baz.x = 1; + reader.get('foo').baz.x = 1; + reader.get('foo.baz').x = 1; + reader.get().x.y.z.w = 1; + reader.get('x').y.z.w = 1; + reader.get('x.y').z.w = 1; + reader.get('x.y.z').w = 1; + + const readerSingle = ConfigReader.fromConfigs([ + { data: data1, context: '1' }, + ]); + + readerSingle.get().foo.bar.push(1); + readerSingle.get('foo').bar.push(1); + readerSingle.get('foo.bar').push(1); + readerSingle.get().foo.baz.x = 1; + readerSingle.get('foo').baz.x = 1; + readerSingle.get('foo.baz').x = 1; + + expect(data1.foo.bar).toEqual([]); + expect(data1.foo.baz).toEqual({}); + expect(data2.x.y.z).toEqual({}); + }); }); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 8c0c4140a5..129a703784 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -126,7 +126,7 @@ export class ConfigReader implements Config { /** {@inheritdoc Config.getOptional} */ getOptional(key?: string): T | undefined { - const value = this.readValue(key); + const value = cloneDeep(this.readValue(key)); const fallbackValue = this.fallback?.getOptional(key); if (value === undefined) { @@ -153,11 +153,8 @@ export class ConfigReader implements Config { // Avoid merging arrays and primitive values, since that's how merging works for other // methods for reading config. - return mergeWith( - {}, - { value: cloneDeep(fallbackValue) }, - { value }, - (into, from) => (!isObject(from) || !isObject(into) ? from : undefined), + return mergeWith({}, { value: fallbackValue }, { value }, (into, from) => + !isObject(from) || !isObject(into) ? from : undefined, ).value as T; } From 2b19fd2e941b6b3a00abc96a519597c40e290a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 8 Jan 2022 16:30:45 +0100 Subject: [PATCH 12/16] deep clone data read out of config, to avoid pollution / mutation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/fuzzy-llamas-collect.md | 5 + .../src/ldap/config.test.ts | 84 +++++++++ .../src/ldap/config.ts | 13 +- .../src/ldap/read.test.ts | 169 +++++++++++++++++- .../src/ldap/read.ts | 5 +- 5 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 .changeset/fuzzy-llamas-collect.md diff --git a/.changeset/fuzzy-llamas-collect.md b/.changeset/fuzzy-llamas-collect.md new file mode 100644 index 0000000000..17ee925e56 --- /dev/null +++ b/.changeset/fuzzy-llamas-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Make sure to avoid accidental data sharing / mutation of `set` values diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 9fb536a87f..06ac24d3ae 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -209,4 +209,88 @@ describe('readLdapConfig', () => { const expected = '(|(cn=foo bar)(cn=bar))'; expect(actual[0].users.options.filter).toEqual(expected); }); + + it('supports a dot nested set structure', () => { + const config = { + providers: [ + { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + 'metadata.annotations': { + a: 'b', + }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + }, + ], + }; + const actual = readLdapConfig(new ConfigReader(config)); + + expect(actual[0].users.set).toEqual({ 'metadata.annotations': { a: 'b' } }); + }); + + it('throws on attempts to modify the set structure', () => { + const config = { + providers: [ + { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + }, + ], + }; + const actual = readLdapConfig(new ConfigReader(config)); + + expect(() => { + (actual[0].users.set as any).y = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property y, object is not extensible"`, + ); + expect(() => { + (actual[0].users.set as any).x.b = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property b, object is not extensible"`, + ); + + expect(() => { + (actual[0].groups.set as any).y = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property y, object is not extensible"`, + ); + expect(() => { + (actual[0].groups.set as any).x.b = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property b, object is not extensible"`, + ); + }); }); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index dc1ec4910d..b12c7c9fee 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -166,6 +166,15 @@ const defaultConfig = { * @param config The root of the LDAP config hierarchy */ export function readLdapConfig(config: Config): LdapProviderConfig[] { + function freeze(data: T): T { + return JSON.parse(JSON.stringify(data), (_key, value) => { + if (typeof value === 'object' && value !== null) { + Object.freeze(value); + } + return value; + }); + } + function readBindConfig( c: Config | undefined, ): LdapProviderConfig['bind'] | undefined { @@ -217,7 +226,7 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { if (!c) { return undefined; } - return Object.fromEntries(c.keys().map(path => [path, c.get(path)])); + return c.get(); } function readUserMapConfig( @@ -297,6 +306,6 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { // Replace arrays instead of merging, otherwise default behavior return Array.isArray(from) ? from : undefined; }); - return merged as LdapProviderConfig; + return freeze(merged) as LdapProviderConfig; }); } diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index c4f203c5db..875ffb7029 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -25,7 +25,13 @@ import { LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION, } from './constants'; -import { readLdapGroups, readLdapUsers, resolveRelations } from './read'; +import { + defaultGroupTransformer, + defaultUserTransformer, + readLdapGroups, + readLdapUsers, + resolveRelations, +} from './read'; import { ActiveDirectoryVendor, DefaultLdapVendor } from './vendors'; function user(data: RecursivePartial): UserEntity { @@ -264,6 +270,7 @@ describe('readLdapGroups', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + it('transfers all attributes from Microsoft Active Directory', async () => { client.getVendor.mockResolvedValue(ActiveDirectoryVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { @@ -358,6 +365,7 @@ describe('resolveRelations', () => { expect(parent.spec.children).toEqual(['child']); expect(child.spec.parent).toEqual('parent'); }); + it('matches by UUID', () => { const parent = group({ metadata: { @@ -539,3 +547,162 @@ describe('resolveRelations', () => { }); }); }); + +describe('defaultUserTransformer', () => { + it('can set things safely', async () => { + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, + set: { + 'metadata.annotations.a': 1, + 'metadata.annotations': { a: 2, b: 3 }, + }, + }; + + const entry = searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + let output = await defaultUserTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + name: 'uid-value', + }, + spec: { + memberOf: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + + (output!.metadata.annotations as any).c = 7; + + // exact same inputs again + output = await defaultUserTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + name: 'uid-value', + }, + spec: { + memberOf: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + }); +}); + +describe('defaultGroupTransformer', () => { + it('can set things safely', async () => { + const config: GroupConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + description: 'description', + type: 'type', + members: 'members', + memberOf: 'memberOf', + }, + set: { + 'metadata.annotations.a': 1, + 'metadata.annotations': { a: 2, b: 3 }, + }, + }; + + const entry = searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + let output = await defaultGroupTransformer( + DefaultLdapVendor, + config, + entry, + ); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + description: 'description-value', + name: 'uid-value', + }, + spec: { + type: 'unknown', + children: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + + (output!.metadata.annotations as any).c = 7; + + // exact same inputs again + output = await defaultGroupTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + description: 'description-value', + name: 'uid-value', + }, + spec: { + type: 'unknown', + children: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + }); +}); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 2d6e3c1608..513d899304 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -17,6 +17,7 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { SearchEntry } from 'ldapjs'; import lodashSet from 'lodash/set'; +import cloneDeep from 'lodash/cloneDeep'; import { buildOrgHierarchy } from './org'; import { LdapClient } from './client'; import { GroupConfig, UserConfig } from './config'; @@ -52,7 +53,7 @@ export async function defaultUserTransformer( if (set) { for (const [path, value] of Object.entries(set)) { - lodashSet(entity, path, value); + lodashSet(entity, path, cloneDeep(value)); } } @@ -146,7 +147,7 @@ export async function defaultGroupTransformer( if (set) { for (const [path, value] of Object.entries(set)) { - lodashSet(entity, path, value); + lodashSet(entity, path, cloneDeep(value)); } } From 722681b1b13ff2192a0c3a3389d7fc7d0b752b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 9 Jan 2022 13:05:10 +0100 Subject: [PATCH 13/16] Clean up API reports in ldap and msgraph catalog modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/long-otters-promise.md | 6 ++ .gitignore | 3 + .../catalog-backend-module-ldap/api-report.md | 77 +++---------------- .../src/ldap/client.ts | 20 +++-- .../src/ldap/config.ts | 12 ++- .../src/ldap/constants.ts | 6 ++ .../src/ldap/index.ts | 7 +- .../src/ldap/read.ts | 54 ++++++++----- .../src/ldap/types.ts | 26 +++++-- .../src/ldap/util.ts | 18 +++-- .../src/ldap/vendors.ts | 6 +- .../src/processors/LdapOrgEntityProvider.ts | 13 ++++ .../src/processors/LdapOrgReaderProcessor.ts | 2 + .../api-report.md | 65 ++++------------ .../src/microsoftGraph/client.ts | 5 ++ .../src/microsoftGraph/config.ts | 11 ++- .../src/microsoftGraph/constants.ts | 6 ++ .../src/microsoftGraph/helper.ts | 5 ++ .../src/microsoftGraph/index.ts | 1 + .../src/microsoftGraph/read.ts | 24 ++++++ .../src/microsoftGraph/types.ts | 15 ++++ .../MicrosoftGraphOrgEntityProvider.ts | 9 +++ .../MicrosoftGraphOrgReaderProcessor.ts | 2 + 23 files changed, 225 insertions(+), 168 deletions(-) create mode 100644 .changeset/long-otters-promise.md diff --git a/.changeset/long-otters-promise.md b/.changeset/long-otters-promise.md new file mode 100644 index 0000000000..eecb97e8ac --- /dev/null +++ b/.changeset/long-otters-promise.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Clean up API report diff --git a/.gitignore b/.gitignore index 8c37280023..60d0d6da3b 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,6 @@ site # e2e tests cypress/cypress/* + +# Possible leftover from build:api-reports +tsconfig.tmp.json diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index bbf92944b9..e2dbef74e5 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -17,26 +17,26 @@ import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { UserEntity } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "defaultGroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type BindConfig = { + dn: string; + secret: string; +}; + +// @public export function defaultGroupTransformer( vendor: LdapVendor, config: GroupConfig, entry: SearchEntry, ): Promise; -// Warning: (ae-missing-release-tag) "defaultUserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultUserTransformer( vendor: LdapVendor, config: UserConfig, entry: SearchEntry, ): Promise; -// Warning: (ae-missing-release-tag) "GroupConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type GroupConfig = { dn: string; @@ -57,12 +57,6 @@ export type GroupConfig = { }; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration -// Warning: (ae-missing-release-tag) "GroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type GroupTransformer = ( vendor: LdapVendor, @@ -70,28 +64,18 @@ export type GroupTransformer = ( group: SearchEntry, ) => Promise; -// Warning: (ae-missing-release-tag) "LDAP_DN_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; -// Warning: (ae-missing-release-tag) "LDAP_RDN_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; -// Warning: (ae-missing-release-tag) "LDAP_UUID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; -// Warning: (ae-missing-release-tag) "LdapClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapClient { constructor(client: Client, logger: Logger_2); - // Warning: (ae-forgotten-export) The symbol "BindConfig" needs to be exported by the entry point index.d.ts - // // (undocumented) static create( logger: Logger_2, @@ -100,22 +84,14 @@ export class LdapClient { ): Promise; getRootDSE(): Promise; getVendor(): Promise; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen search(dn: string, options: SearchOptions): Promise; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (ae-forgotten-export) The symbol "SearchCallback" needs to be exported by the entry point index.d.ts searchStreaming( dn: string, options: SearchOptions, - f: SearchCallback, + f: (entry: SearchEntry) => void, ): Promise; } -// Warning: (ae-missing-release-tag) "LdapOrgEntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapOrgEntityProvider implements EntityProvider { constructor(options: { @@ -140,12 +116,9 @@ export class LdapOrgEntityProvider implements EntityProvider { ): LdapOrgEntityProvider; // (undocumented) getProviderName(): string; - // (undocumented) read(): Promise; } -// Warning: (ae-missing-release-tag) "LdapOrgReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -171,8 +144,6 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "LdapProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type LdapProviderConfig = { target: string; @@ -181,8 +152,6 @@ export type LdapProviderConfig = { groups: GroupConfig; }; -// Warning: (ae-missing-release-tag) "LdapVendor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type LdapVendor = { dnAttributeName: string; @@ -190,12 +159,6 @@ export type LdapVendor = { decodeStringAttribute: (entry: SearchEntry, name: string) => string[]; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "mapStringAttr" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function mapStringAttr( entry: SearchEntry, @@ -204,18 +167,9 @@ export function mapStringAttr( setter: (value: string) => void, ): void; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "readLdapConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function readLdapConfig(config: Config): LdapProviderConfig[]; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "readLdapOrg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function readLdapOrg( client: LdapClient, @@ -231,8 +185,6 @@ export function readLdapOrg( groups: GroupEntity[]; }>; -// Warning: (ae-missing-release-tag) "UserConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type UserConfig = { dn: string; @@ -251,21 +203,10 @@ export type UserConfig = { }; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration -// Warning: (ae-missing-release-tag) "UserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type UserTransformer = ( vendor: LdapVendor, config: UserConfig, user: SearchEntry, ) => Promise; - -// Warnings were encountered during analysis: -// -// src/ldap/vendors.d.ts:17:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/ldap/vendors.d.ts:18:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen ``` diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 553081f4b0..ea13ba49ee 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -25,14 +25,12 @@ import { LdapVendor, } from './vendors'; -export interface SearchCallback { - (entry: SearchEntry): void; -} - /** - * Basic wrapper for the ldapjs library. + * Basic wrapper for the `ldapjs` library. * * Helps out with promisifying calls, paging, binding etc. + * + * @public */ export class LdapClient { private vendor: Promise | undefined; @@ -75,8 +73,8 @@ export class LdapClient { /** * Performs an LDAP search operation. * - * @param dn The fully qualified base DN to search within - * @param options The search options + * @param dn - The fully qualified base DN to search within + * @param options - The search options */ async search(dn: string, options: SearchOptions): Promise { try { @@ -128,14 +126,14 @@ export class LdapClient { /** * Performs an LDAP search operation, calls a function on each entry to limit memory usage * - * @param dn The fully qualified base DN to search within - * @param options The search options - * @param f The callback to call on each search entry + * @param dn - The fully qualified base DN to search within + * @param options - The search options + * @param f - The callback to call on each search entry */ async searchStreaming( dn: string, options: SearchOptions, - f: SearchCallback, + f: (entry: SearchEntry) => void, ): Promise { try { return await new Promise((resolve, reject) => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index b12c7c9fee..526467962e 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -23,6 +23,8 @@ import { trimEnd } from 'lodash'; /** * The configuration parameters for a single LDAP provider. + * + * @public */ export type LdapProviderConfig = { // The prefix of the target that this matches on, e.g. @@ -39,6 +41,8 @@ export type LdapProviderConfig = { /** * The settings to use for the a command. + * + * @public */ export type BindConfig = { // The DN of the user to auth as, e.g. @@ -50,6 +54,8 @@ export type BindConfig = { /** * The settings that govern the reading and interpretation of users. + * + * @public */ export type UserConfig = { // The DN under which users are stored. @@ -88,6 +94,8 @@ export type UserConfig = { /** * The settings that govern the reading and interpretation of groups. + * + * @public */ export type GroupConfig = { // The DN under which groups are stored. @@ -163,7 +171,9 @@ const defaultConfig = { /** * Parses configuration. * - * @param config The root of the LDAP config hierarchy + * @param config - The root of the LDAP config hierarchy + * + * @public */ export function readLdapConfig(config: Config): LdapProviderConfig[] { function freeze(data: T): T { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/constants.ts b/plugins/catalog-backend-module-ldap/src/ldap/constants.ts index 73df5d6de8..cdc448b4dd 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/constants.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/constants.ts @@ -22,6 +22,8 @@ * example, for an item with the fully qualified DN * uid=john,ou=people,ou=spotify,dc=spotify,dc=net the generated entity would * have this annotation, with the value "john". + * + * @public */ export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; @@ -33,6 +35,8 @@ export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; * for an item with the DN uid=john,ou=people,ou=spotify,dc=spotify,dc=net the * generated entity would have this annotation, with that full string as its * value. + * + * @public */ export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; @@ -44,5 +48,7 @@ export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; * for an item with the UUID 76ef928a-b251-1037-9840-d78227f36a7e, the * generated entity would have this annotation, with that full string as its * value. + * + * @public */ export const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index 70500299ae..c3aace492f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -17,7 +17,12 @@ export { LdapClient } from './client'; export { mapStringAttr } from './util'; export { readLdapConfig } from './config'; -export type { LdapProviderConfig, GroupConfig, UserConfig } from './config'; +export type { + LdapProviderConfig, + GroupConfig, + UserConfig, + BindConfig, +} from './config'; export type { LdapVendor } from './vendors'; export { LDAP_DN_ANNOTATION, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 513d899304..e15ec85554 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -31,6 +31,12 @@ import { Logger } from 'winston'; import { GroupTransformer, UserTransformer } from './types'; import { mapStringAttr } from './util'; +/** + * The default implementation of the transformation from an LDAP entry to a + * User entity. + * + * @public + */ export async function defaultUserTransformer( vendor: LdapVendor, config: UserConfig, @@ -88,9 +94,9 @@ export async function defaultUserTransformer( /** * Reads users out of an LDAP provider. * - * @param client The LDAP client - * @param config The user data configuration - * @param opts + * @param client - The LDAP client + * @param config - The user data configuration + * @param opts - Additional options */ export async function readLdapUsers( client: LdapClient, @@ -125,6 +131,12 @@ export async function readLdapUsers( return { users: entities, userMemberOf }; } +/** + * The default implementation of the transformation from an LDAP entry to a + * Group entity. + * + * @public + */ export async function defaultGroupTransformer( vendor: LdapVendor, config: GroupConfig, @@ -185,9 +197,9 @@ export async function defaultGroupTransformer( /** * Reads groups out of an LDAP provider. * - * @param client The LDAP client - * @param config The group data configuration - * @param opts + * @param client - The LDAP client + * @param config - The group data configuration + * @param opts - Additional options */ export async function readLdapGroups( client: LdapClient, @@ -240,13 +252,12 @@ export async function readLdapGroups( /** * Reads users and groups out of an LDAP provider. * - * Invokes the above "raw" read functions and stitches together the results - * with all relations etc filled in. + * @param client - The LDAP client + * @param userConfig - The user data configuration + * @param groupConfig - The group data configuration + * @param options - Additional options * - * @param client The LDAP client - * @param userConfig The user data configuration - * @param groupConfig The group data configuration - * @param options + * @public */ export async function readLdapOrg( client: LdapClient, @@ -261,6 +272,9 @@ export async function readLdapOrg( users: UserEntity[]; groups: GroupEntity[]; }> { + // Invokes the above "raw" read functions and stitches together the results + // with all relations etc filled in. + const { users, userMemberOf } = await readLdapUsers(client, userConfig, { transformer: options?.userTransformer, }); @@ -321,14 +335,14 @@ function ensureItems( * Takes groups and entities with empty relations, and fills in the various * relations that were returned by the readers, and forms the org hierarchy. * - * @param groups Group entities with empty relations; modified in place - * @param users User entities with empty relations; modified in place - * @param userMemberOf For a user DN, the set of group DNs or UUIDs that the - * user is a member of - * @param groupMemberOf For a group DN, the set of group DNs or UUIDs that the - * group is a member of (parents in the hierarchy) - * @param groupMember For a group DN, the set of group DNs or UUIDs that are - * members of the group (children in the hierarchy) + * @param groups - Group entities with empty relations; modified in place + * @param users - User entities with empty relations; modified in place + * @param userMemberOf - For a user DN, the set of group DNs or UUIDs that the + * user is a member of + * @param groupMemberOf - For a group DN, the set of group DNs or UUIDs that + * the group is a member of (parents in the hierarchy) + * @param groupMember - For a group DN, the set of group DNs or UUIDs that are + * members of the group (children in the hierarchy) */ export function resolveRelations( groups: GroupEntity[], diff --git a/plugins/catalog-backend-module-ldap/src/ldap/types.ts b/plugins/catalog-backend-module-ldap/src/ldap/types.ts index e32e4c5b42..9d7c326add 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/types.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/types.ts @@ -21,10 +21,15 @@ import { GroupConfig, UserConfig } from './config'; /** * Customize the ingested User entity * - * @param vendor The LDAP vendor that can be used to find and decode vendor specific attributes - * @param config The User specific config used by the default transformer. - * @param user The found LDAP entry in its source format. This is the entry that you want to transform - * @return A `UserEntity` or `undefined` if you want to ignore the found user for being ingested by the catalog + * @param vendor - The LDAP vendor that can be used to find and decode vendor + * specific attributes + * @param config - The User specific config used by the default transformer. + * @param user - The found LDAP entry in its source format. This is the entry + * that you want to transform + * @returns A `UserEntity` or `undefined` if you want to ignore the found user + * for being ingested by the catalog + * + * @public */ export type UserTransformer = ( vendor: LdapVendor, @@ -35,10 +40,15 @@ export type UserTransformer = ( /** * Customize the ingested Group entity * - * @param vendor The LDAP vendor that can be used to find and decode vendor specific attributes - * @param config The Group specific config used by the default transformer. - * @param group The found LDAP entry in its source format. This is the entry that you want to transform - * @return A `GroupEntity` or `undefined` if you want to ignore the found group for being ingested by the catalog + * @param vendor - The LDAP vendor that can be used to find and decode vendor + * specific attributes + * @param config - The Group specific config used by the default transformer. + * @param group - The found LDAP entry in its source format. This is the entry + * that you want to transform + * @returns A `GroupEntity` or `undefined` if you want to ignore the found group + * for being ingested by the catalog + * + * @public */ export type GroupTransformer = ( vendor: LdapVendor, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/util.ts b/plugins/catalog-backend-module-ldap/src/ldap/util.ts index 21ee40f52c..21829cef35 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/util.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/util.ts @@ -20,19 +20,25 @@ import { LdapVendor } from './vendors'; /** * Builds a string form of an LDAP Error structure. * - * @param error The error + * @param error - The error */ export function errorString(error: LDAPError) { return `${error.code} ${error.name}: ${error.message}`; } /** - * Maps a single-valued attribute to a consumer + * Maps a single-valued attribute to a consumer. * - * @param entry The LDAP source entry - * @param vendor The LDAP vendor - * @param attributeName The source attribute to map. If the attribute is undefined the mapping will be silently ignored. - * @param setter The function to be called with the decoded attribute from the source entry + * This helper can be useful when implementing a user or group transformer. + * + * @param entry - The LDAP source entry + * @param vendor - The LDAP vendor + * @param attributeName - The source attribute to map. If the attribute is + * undefined the mapping will be silently ignored. + * @param setter - The function to be called with the decoded attribute from the + * source entry + * + * @public */ export function mapStringAttr( entry: SearchEntry, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts index 3341497ca8..3df29a5fb9 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts @@ -18,6 +18,8 @@ import { SearchEntry } from 'ldapjs'; /** * An LDAP Vendor handles unique nuances between different vendors. + * + * @public */ export type LdapVendor = { /** @@ -31,8 +33,8 @@ export type LdapVendor = { /** * Decode ldap entry values for a given attribute name to their string representation. * - * @param entry The ldap entry - * @param name The attribute to decode + * @param entry - The ldap entry + * @param name - The attribute to decode */ decodeStringAttribute: (entry: SearchEntry, name: string) => string[]; }; diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index c0cd663052..c74e5b3aee 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -39,6 +39,13 @@ import { /** * Reads user and group entries out of an LDAP service, and provides them as * User and Group entities for the catalog. + * + * @remarks + * + * Add an instance of this class to your catalog builder, and then periodically + * call the {@link LdapOrgEntityProvider.read} method. + * + * @public */ export class LdapOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; @@ -113,14 +120,20 @@ export class LdapOrgEntityProvider implements EntityProvider { }, ) {} + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { return `LdapOrgEntityProvider:${this.options.id}`; } + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; } + /** + * Runs one complete ingestion loop. Call this method regularly at some + * appropriate cadence. + */ async read() { if (!this.connection) { throw new Error('Not initialized'); diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index f87afc38b9..be2d748840 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -33,6 +33,8 @@ import { /** * Extracts teams and users out of an LDAP server. + * + * @public */ export class LdapOrgReaderProcessor implements CatalogProcessor { private readonly providers: LdapProviderConfig[]; diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index e1a837d0c1..662056eff1 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -16,51 +16,37 @@ import * as msal from '@azure/msal-node'; import { Response as Response_2 } from 'node-fetch'; import { UserEntity } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "defaultGroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, ): Promise; -// Warning: (ae-missing-release-tag) "defaultOrganizationTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultOrganizationTransformer( organization: MicrosoftGraph.Organization, ): Promise; -// Warning: (ae-missing-release-tag) "defaultUserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, ): Promise; -// Warning: (ae-missing-release-tag) "GroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type GroupTransformer = ( group: MicrosoftGraph.Group, groupPhoto?: string, ) => Promise; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_GROUP_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION = 'graph.microsoft.com/group-id'; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_TENANT_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION = 'graph.microsoft.com/tenant-id'; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_USER_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; @@ -76,7 +62,6 @@ export class MicrosoftGraphClient { groupId: string, maxSize: number, ): Promise; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" getGroups(query?: ODataQuery): AsyncIterable; getOrganization(tenantId: string): Promise; // (undocumented) @@ -86,18 +71,12 @@ export class MicrosoftGraphClient { maxSize: number, ): Promise; getUserProfile(userId: string): Promise; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" getUsers(query?: ODataQuery): AsyncIterable; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" requestApi(path: string, query?: ODataQuery): Promise; - // Warning: (ae-forgotten-export) The symbol "ODataQuery" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" requestCollection(path: string, query?: ODataQuery): AsyncIterable; requestRaw(url: string): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphOrgEntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class MicrosoftGraphOrgEntityProvider implements EntityProvider { constructor(options: { @@ -124,12 +103,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { ): MicrosoftGraphOrgEntityProvider; // (undocumented) getProviderName(): string; - // (undocumented) read(): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphOrgReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -157,8 +133,6 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type MicrosoftGraphProviderConfig = { target: string; @@ -171,28 +145,27 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; }; -// Warning: (ae-missing-release-tag) "normalizeEntityName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function normalizeEntityName(name: string): string; -// Warning: (ae-missing-release-tag) "OrganizationTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type ODataQuery = { + filter?: string; + expand?: string[]; + select?: string[]; +}; + +// @public export type OrganizationTransformer = ( organization: MicrosoftGraph.Organization, ) => Promise; -// Warning: (ae-missing-release-tag) "readMicrosoftGraphConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function readMicrosoftGraphConfig( config: Config, ): MicrosoftGraphProviderConfig[]; -// Warning: (ae-missing-release-tag) "readMicrosoftGraphOrg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, @@ -210,15 +183,9 @@ export function readMicrosoftGraphOrg( groups: GroupEntity[]; }>; -// Warning: (ae-missing-release-tag) "UserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type UserTransformer = ( user: MicrosoftGraph.User, userPhoto?: string, ) => Promise; - -// Warnings were encountered during analysis: -// -// src/microsoftGraph/config.d.ts:28:8 - (tsdoc-undefined-tag) The TSDoc tag "@visibility" is not defined in this configuration ``` diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 63f256afbd..827c9fc14d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -41,6 +41,11 @@ export type ODataQuery = { select?: string[]; }; +/** + * Extends the base msgraph types to include the odata type. + * + * @public + */ export type GroupMember = | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' }) | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 91a6d49b57..4d37e24632 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -19,6 +19,8 @@ import { trimEnd } from 'lodash'; /** * The configuration parameters for a single Microsoft Graph provider. + * + * @public */ export type MicrosoftGraphProviderConfig = { /** @@ -42,8 +44,6 @@ export type MicrosoftGraphProviderConfig = { clientId: string; /** * The OAuth client secret to use for authenticating requests. - * - * @visibility secret */ clientSecret: string; /** @@ -66,6 +66,13 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; }; +/** + * Parses configuration. + * + * @param config - The root of the msgraph config hierarchy + * + * @public + */ export function readMicrosoftGraphConfig( config: Config, ): MicrosoftGraphProviderConfig[] { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts index e6abbebb0a..bbfbd61efe 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts @@ -16,17 +16,23 @@ /** * The tenant id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION = 'graph.microsoft.com/tenant-id'; /** * The group id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION = 'graph.microsoft.com/group-id'; /** * The user id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts index 41dbc1aed8..fd09d754d4 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * Takes an input string and cleans it up to become suitable as an entity name. + * + * @public + */ export function normalizeEntityName(name: string): string { let cleaned = name .trim() diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts index eeb436d317..b0d53b43a2 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ export { MicrosoftGraphClient } from './client'; +export type { ODataQuery } from './client'; export { readMicrosoftGraphConfig } from './config'; export type { MicrosoftGraphProviderConfig } from './config'; export { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 7dd6f83e2e..b2845ac78d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { GroupEntity, stringifyEntityRef, @@ -35,6 +36,12 @@ import { UserTransformer, } from './types'; +/** + * The default implementation of the transformation from a graph user entry to + * a User entity. + * + * @public + */ export async function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, @@ -208,6 +215,12 @@ export async function readMicrosoftGraphUsersInGroups( return { users }; } +/** + * The default implementation of the transformation from a graph organization + * entry to a Group entity. + * + * @public + */ export async function defaultOrganizationTransformer( organization: MicrosoftGraph.Organization, ): Promise { @@ -258,6 +271,12 @@ function extractGroupName(group: MicrosoftGraph.Group): string { return (group.mailNickname || group.displayName) as string; } +/** + * The default implementation of the transformation from a graph group entry to + * a Group entity. + * + * @public + */ export async function defaultGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, @@ -472,6 +491,11 @@ export function resolveRelations( buildMemberOf(groups, users); } +/** + * Reads an entire org as Group and User entities. + * + * @public + */ export async function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts index fab662cc3f..ef60d7b018 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts @@ -17,15 +17,30 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +/** + * Customize the ingested User entity + * + * @public + */ export type UserTransformer = ( user: MicrosoftGraph.User, userPhoto?: string, ) => Promise; +/** + * Customize the ingested organization Group entity + * + * @public + */ export type OrganizationTransformer = ( organization: MicrosoftGraph.Organization, ) => Promise; +/** + * Customize the ingested Group entity + * + * @public + */ export type GroupTransformer = ( group: MicrosoftGraph.Group, groupPhoto?: string, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index ea396162dd..6d66723970 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Entity, LOCATION_ANNOTATION, @@ -41,6 +42,8 @@ import { /** * Reads user and group entries out of Microsoft Graph, and provides them as * User and Group entities for the catalog. + * + * @public */ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; @@ -91,14 +94,20 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { }, ) {} + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { return `MicrosoftGraphOrgEntityProvider:${this.options.id}`; } + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; } + /** + * Runs one complete ingestion loop. Call this method regularly at some + * appropriate cadence. + */ async read() { if (!this.connection) { throw new Error('Not initialized'); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index cb7729e04a..351a4983d9 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -34,6 +34,8 @@ import { /** * Extracts teams and users out of a the Microsoft Graph API. + * + * @public */ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { private readonly providers: MicrosoftGraphProviderConfig[]; From f77bd5c8ff955d2842579706297cc70e25fd5f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 9 Jan 2022 15:21:44 +0100 Subject: [PATCH 14/16] Clean up API reports in backend-common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/twelve-panthers-move.md | 5 ++ packages/backend-common/api-report.md | 53 ++++++------- .../backend-common/src/cache/CacheClient.ts | 6 +- .../backend-common/src/cache/CacheManager.ts | 7 +- packages/backend-common/src/cache/types.ts | 24 ++++-- .../src/database/DatabaseManager.ts | 75 +++++++++++-------- .../backend-common/src/database/connection.ts | 11 ++- packages/backend-common/src/database/types.ts | 2 +- .../backend-common/src/logging/formats.ts | 6 +- .../backend-common/src/logging/rootLogger.ts | 33 +++++++- .../src/middleware/errorHandler.ts | 6 +- .../src/middleware/statusCheckHandler.ts | 13 +++- .../src/reading/AwsS3UrlReader.ts | 5 ++ .../src/reading/AzureUrlReader.ts | 6 +- .../src/reading/BitbucketUrlReader.ts | 4 +- .../src/reading/FetchUrlReader.ts | 2 +- .../src/reading/GithubUrlReader.ts | 2 +- .../src/reading/GitlabUrlReader.ts | 6 +- .../src/reading/GoogleGcsUrlReader.ts | 6 +- .../backend-common/src/reading/UrlReaders.ts | 13 +++- packages/backend-common/src/reading/types.ts | 7 +- packages/backend-common/src/scm/git.ts | 6 +- .../src/service/createStatusCheckRouter.ts | 19 ++++- packages/backend-common/src/service/types.ts | 12 ++- .../src/util/ContainerRunner.ts | 15 +++- .../src/util/DockerContainerRunner.ts | 6 +- 26 files changed, 246 insertions(+), 104 deletions(-) create mode 100644 .changeset/twelve-panthers-move.md diff --git a/.changeset/twelve-panthers-move.md b/.changeset/twelve-panthers-move.md new file mode 100644 index 0000000000..f52050a488 --- /dev/null +++ b/.changeset/twelve-panthers-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Clean up API reports diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index a62a280dc9..d63416571d 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -34,9 +34,7 @@ import { Server } from 'http'; import * as winston from 'winston'; import { Writable } from 'stream'; -// Warning: (ae-missing-release-tag) "AwsS3UrlReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class AwsS3UrlReader implements UrlReader { constructor( integration: AwsS3Integration, @@ -59,7 +57,7 @@ export class AwsS3UrlReader implements UrlReader { toString(): string; } -// @public (undocumented) +// @public export class AzureUrlReader implements UrlReader { constructor( integration: AzureIntegration, @@ -114,12 +112,12 @@ export interface CacheClient { ): Promise; } -// @public (undocumented) +// @public export type CacheClientOptions = { defaultTtl?: number; }; -// @public (undocumented) +// @public export type CacheClientSetOptions = { ttl?: number; }; @@ -133,18 +131,17 @@ export class CacheManager { ): CacheManager; } -// @public (undocumented) +// @public export type CacheManagerOptions = { logger?: Logger_2; onError?: (err: Error) => void; }; -// @public (undocumented) +// @public export const coloredFormat: winston.Logform.Format; -// @public (undocumented) +// @public export interface ContainerRunner { - // (undocumented) runContainer(opts: RunContainerOptions): Promise; } @@ -157,7 +154,7 @@ export function createDatabaseClient( overrides?: Partial, ): Knex; -// @public (undocumented) +// @public export function createRootLogger( options?: winston.LoggerOptions, env?: NodeJS.ProcessEnv, @@ -166,14 +163,14 @@ export function createRootLogger( // @public export function createServiceBuilder(_module: NodeModule): ServiceBuilder; -// @public (undocumented) +// @public export function createStatusCheckRouter(options: { logger: Logger_2; path?: string; statusCheck?: StatusCheck; }): Promise; -// @public (undocumented) +// @public export class DatabaseManager { forPlugin(pluginId: string): PluginDatabaseManager; static fromConfig( @@ -187,7 +184,7 @@ export type DatabaseManagerOptions = { migrations?: PluginDatabaseManager['migrations']; }; -// @public (undocumented) +// @public export class DockerContainerRunner implements ContainerRunner { constructor(options: { dockerClient: Docker }); // (undocumented) @@ -205,7 +202,7 @@ export function errorHandler( options?: ErrorHandlerOptions, ): ErrorRequestHandler; -// @public (undocumented) +// @public export type ErrorHandlerOptions = { showStackTraces?: boolean; logger?: Logger_2; @@ -218,13 +215,13 @@ export type FromReadableArrayOptions = Array<{ path: string; }>; -// @public (undocumented) +// @public export function getRootLogger(): winston.Logger; // @public export function getVoidLogger(): winston.Logger; -// @public (undocumented) +// @public export class Git { // (undocumented) add(options: { dir: string; filepath: string }): Promise; @@ -310,7 +307,7 @@ export class GithubUrlReader implements UrlReader { toString(): string; } -// @public (undocumented) +// @public export class GitlabUrlReader implements UrlReader { constructor( integration: GitLabIntegration, @@ -398,7 +395,7 @@ export type ReadTreeResponseDirOptions = { targetDir?: string; }; -// @public (undocumented) +// @public export interface ReadTreeResponseFactory { // (undocumented) fromReadableArray( @@ -448,7 +445,7 @@ export type ReadUrlResponse = { // @public export function requestLoggingHandler(logger?: Logger_2): RequestHandler; -// @public (undocumented) +// @public export type RequestLoggingHandlerFactory = ( logger?: Logger_2, ) => RequestHandler; @@ -459,7 +456,7 @@ export function resolvePackagePath(name: string, ...paths: string[]): string; // @public export function resolveSafeChildPath(base: string, path: string): string; -// @public (undocumented) +// @public export type RunContainerOptions = { imageName: string; command?: string | string[]; @@ -508,7 +505,7 @@ export class ServerTokenManager implements TokenManager { static noop(): TokenManager; } -// @public (undocumented) +// @public export type ServiceBuilder = { loadConfig(config: Config): ServiceBuilder; setPort(port: number): ServiceBuilder; @@ -534,7 +531,7 @@ export type ServiceBuilder = { start(): Promise; }; -// @public (undocumented) +// @public export function setRootLogger(newLogger: winston.Logger): void; // @public @deprecated @@ -554,7 +551,7 @@ export class SingleHostDiscovery implements PluginEndpointDiscovery { getExternalBaseUrl(pluginId: string): Promise; } -// @public (undocumented) +// @public export type StatusCheck = () => Promise; // @public @@ -562,7 +559,7 @@ export function statusCheckHandler( options?: StatusCheckHandlerOptions, ): Promise; -// @public (undocumented) +// @public export interface StatusCheckHandlerOptions { statusCheck?: StatusCheck; } @@ -597,7 +594,7 @@ export class UrlReaders { static default(options: UrlReadersOptions): UrlReader; } -// @public (undocumented) +// @public export type UrlReadersOptions = { config: Config; logger: Logger_2; @@ -612,8 +609,4 @@ export function useHotCleanup( // @public export function useHotMemoize(_module: NodeModule, valueFactory: () => T): T; - -// Warnings were encountered during analysis: -// -// src/database/types.d.ts:23:12 - (tsdoc-undefined-tag) The TSDoc tag "@default" is not defined in this configuration ``` diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts index 8e046f7f19..96982edeaa 100644 --- a/packages/backend-common/src/cache/CacheClient.ts +++ b/packages/backend-common/src/cache/CacheClient.ts @@ -22,7 +22,11 @@ type CacheClientArgs = { client: Keyv; }; -/** @public */ +/** + * Options passed to {@link CacheClient.set}. + * + * @public + */ export type CacheClientSetOptions = { /** * Optional TTL in milliseconds. Defaults to the TTL provided when the client diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index 896c08fb94..66e9849e77 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -55,8 +55,8 @@ export class CacheManager { private readonly errorHandler: CacheManagerOptions['onError']; /** - * Creates a new CacheManager instance by reading from the `backend` config - * section, specifically the `.cache` key. + * Creates a new {@link CacheManager} instance by reading from the `backend` + * config section, specifically the `.cache` key. * * @param config - The loaded application configuration. */ @@ -93,7 +93,8 @@ export class CacheManager { /** * Generates a PluginCacheManager for consumption by plugins. * - * @param pluginId - The plugin that the cache manager should be created for. Plugin names should be unique. + * @param pluginId - The plugin that the cache manager should be created for. + * Plugin names should be unique. */ forPlugin(pluginId: string): PluginCacheManager { return { diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 70c46770a8..5cf8323c52 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -17,7 +17,11 @@ import { Logger } from 'winston'; import { CacheClient } from './CacheClient'; -/** @public */ +/** + * Options given when constructing a {@link CacheClient}. + * + * @public + */ export type CacheClientOptions = { /** * An optional default TTL (in milliseconds) to be set when getting a client @@ -27,7 +31,11 @@ export type CacheClientOptions = { defaultTtl?: number; }; -/** @public */ +/** + * Options given when constructing a {@link CacheManager}. + * + * @public + */ export type CacheManagerOptions = { /** * An optional logger for use by the PluginCacheManager. @@ -42,17 +50,19 @@ export type CacheManagerOptions = { }; /** - * The PluginCacheManager manages access to cache stores that Plugins get. + * Manages access to cache stores that plugins get. * * @public */ export type PluginCacheManager = { /** - * getClient provides backend plugins cache connections for itself. + * Provides backend plugins cache connections for themselves. * - * The purpose of this method is to allow plugins to get isolated data - * stores so that plugins are discouraged from cache-level integration - * and/or cache key collisions. + * @remarks + * + * The purpose of this method is to allow plugins to get isolated data stores + * so that plugins are discouraged from cache-level integration and/or cache + * key collisions. */ getClient: (options?: CacheClientOptions) => CacheClient; }; diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 5959c60c83..3aadd30148 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -38,7 +38,7 @@ function pluginPath(pluginId: string): string { } /** - * Configuration options object. + * Creation options for {@link DatabaseManager}. * * @public */ @@ -46,15 +46,20 @@ export type DatabaseManagerOptions = { migrations?: PluginDatabaseManager['migrations']; }; -/** @public */ +/** + * Manages database connections for Backstage backend plugins. + * + * The database manager allows the user to set connection and client settings on + * a per pluginId basis by defining a database config block under + * `plugin.` in addition to top level defaults. Optionally, a user may + * set `prefix` which is used to prefix generated database names if config is + * not provided. + * + * @public + */ export class DatabaseManager { /** - * Creates a DatabaseManager from `backend.database` config. - * - * The database manager allows the user to set connection and client settings on a per pluginId - * basis by defining a database config block under `plugin.` in addition to top level - * defaults. Optionally, a user may set `prefix` which is used to prefix generated database - * names if config is not provided. + * Creates a {@link DatabaseManager} from `backend.database` config. * * @param config - The loaded application configuration. * @param options - An optional configuration object. @@ -108,7 +113,7 @@ export class DatabaseManager { * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is * `schema`, it will fallback to using the default database for the knex instance. * - * @param pluginId Lookup the database name for given plugin + * @param pluginId - Lookup the database name for given plugin * @returns String representing the plugin's database name */ private getDatabaseName(pluginId: string): string | undefined { @@ -143,12 +148,13 @@ export class DatabaseManager { /** * Provides the client type which should be used for a given plugin. * - * The client type is determined by plugin specific config if present. Otherwise the base - * client is used as the fallback. + * The client type is determined by plugin specific config if present. + * Otherwise the base client is used as the fallback. * - * @param pluginId Plugin to get the client type for - * @returns Object with client type returned as `client` and boolean representing whether - * or not the client was overridden as `overridden` + * @param pluginId - Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean + * representing whether or not the client was overridden as + * `overridden` */ private getClientType(pluginId: string): { client: string; @@ -169,8 +175,8 @@ export class DatabaseManager { /** * Provides the knexConfig which should be used for a given plugin. * - * @param pluginId Plugin to get the knexConfig for - * @returns the merged kexConfig value or undefined if it isn't specified + * @param pluginId - Plugin to get the knexConfig for + * @returns The merged knexConfig value or undefined if it isn't specified */ private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { const pluginConfig = this.config @@ -197,13 +203,15 @@ export class DatabaseManager { } /** - * Provides a Knex connection plugin config by combining base and plugin config. + * Provides a Knex connection plugin config by combining base and plugin + * config. * - * This method provides a baseConfig for a plugin database connector. If the client type - * has not been overridden, the global connection config will be included with plugin - * specific config as the base. Values from the plugin connection take precedence over the - * base. Base database name is omitted for all supported databases excluding SQLite unless - * `pluginDivisionMode` is set to `schema`. + * This method provides a baseConfig for a plugin database connector. If the + * client type has not been overridden, the global connection config will be + * included with plugin specific config as the base. Values from the plugin + * connection take precedence over the base. Base database name is omitted for + * all supported databases excluding SQLite unless `pluginDivisionMode` is set + * to `schema`. */ private getConnectionConfig( pluginId: string, @@ -249,9 +257,10 @@ export class DatabaseManager { /** * Provides a Knex database config for a given plugin. * - * This method provides a Knex configuration object along with the plugin's client type. + * This method provides a Knex configuration object along with the plugin's + * client type. * - * @param pluginId The plugin that the database config should correspond with + * @param pluginId - The plugin that the database config should correspond with */ private getConfigForPlugin(pluginId: string): Knex.Config { const { client } = this.getClientType(pluginId); @@ -264,20 +273,21 @@ export class DatabaseManager { } /** - * Provides a partial Knex.Config database schema override for a given plugin. + * Provides a partial `Knex.Config` database schema override for a given + * plugin. * - * @param pluginId Target plugin to get database schema override - * @returns Partial Knex.Config with database schema override + * @param pluginId - Target plugin to get database schema override + * @returns Partial `Knex.Config` with database schema override */ private getSchemaOverrides(pluginId: string): Knex.Config | undefined { return createSchemaOverride(this.getClientType(pluginId).client, pluginId); } /** - * Provides a partial Knex.Config database name override for a given plugin. + * Provides a partial `Knex.Config`• database name override for a given plugin. * - * @param pluginId Target plugin to get database name override - * @returns Partial Knex.Config with database name override + * @param pluginId - Target plugin to get database name override + * @returns Partial `Knex.Config` with database name override */ private getDatabaseOverrides(pluginId: string): Knex.Config { const databaseName = this.getDatabaseName(pluginId); @@ -289,8 +299,9 @@ export class DatabaseManager { /** * Provides a scoped Knex client for a plugin as per application config. * - * @param pluginId Plugin to get a Knex client for - * @returns Promise which resolves to a scoped Knex database client for a plugin + * @param pluginId - Plugin to get a Knex client for + * @returns Promise which resolves to a scoped Knex database client for a + * plugin */ private async getDatabase(pluginId: string): Promise { const pluginConfig = new ConfigReader( diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index 3308a26573..7fc9df060d 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -58,7 +58,7 @@ export function createDatabaseClient( } /** - * Alias for createDatabaseClient + * Alias for {@link createDatabaseClient} * * @public * @deprecated Use createDatabaseClient instead @@ -100,7 +100,8 @@ export async function ensureSchemaExists( } /** - * Provides a Knex.Config object with the provided database name for a given client. + * Provides a `Knex.Config` object with the provided database name for a given + * client. */ export function createNameOverride( client: string, @@ -117,7 +118,8 @@ export function createNameOverride( } /** - * Provides a Knex.Config object with the provided database schema for a given client. Currently only supported by `pg`. + * Provides a `Knex.Config` object with the provided database schema for a given + * client. Currently only supported by `pg`. */ export function createSchemaOverride( client: string, @@ -156,7 +158,8 @@ export function parseConnectionString( } /** - * Normalizes a connection config or string into an object which can be passed to Knex. + * Normalizes a connection config or string into an object which can be passed + * to Knex. */ export function normalizeConnection( connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 344f1088b8..2fa8b749a8 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -38,7 +38,7 @@ export interface PluginDatabaseManager { /** * skip database migrations. Useful if connecting to a read-only database. * - * @default false + * @defaultValue false */ skip?: boolean; }; diff --git a/packages/backend-common/src/logging/formats.ts b/packages/backend-common/src/logging/formats.ts index 0477136e63..53eb55e790 100644 --- a/packages/backend-common/src/logging/formats.ts +++ b/packages/backend-common/src/logging/formats.ts @@ -31,7 +31,11 @@ const coloredTemplate = (info: TransformableInfo) => { return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`; }; -/** @public */ +/** + * A logging format that adds coloring to console output. + * + * @public + */ export const coloredFormat = winston.format.combine( winston.format.timestamp(), winston.format.colorize({ diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index 12db7d42a1..87180bde8f 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -23,12 +23,29 @@ import { escapeRegExp } from '../util/escapeRegExp'; let rootLogger: winston.Logger; let redactionRegExp: RegExp | undefined; -/** @public */ +/** + * Gets the current root logger. + * + * @public + */ export function getRootLogger(): winston.Logger { return rootLogger; } -/** @public */ +/** + * Sets a completely custom default "root" logger. + * + * @remarks + * + * This is the logger instance that will be the foundation for all other logger + * instances passed to plugins etc, in a given backend. + * + * Only use this if you absolutely need to make a completely custom logger. + * Normally if you want to make light adaptations to the default logger + * behavior, you would instead call {@link createRootLogger}. + * + * @public + */ export function setRootLogger(newLogger: winston.Logger) { rootLogger = newLogger; } @@ -67,7 +84,17 @@ function redactLogLine(info: winston.Logform.TransformableInfo) { return info; } -/** @public */ +/** + * Creates a default "root" logger. This also calls {@link setRootLogger} under + * the hood. + * + * @remarks + * + * This is the logger instance that will be the foundation for all other logger + * instances passed to plugins etc, in a given backend. + * + * @public + */ export function createRootLogger( options: winston.LoggerOptions = {}, env = process.env, diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 47c9285b18..6077b6a140 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -28,7 +28,11 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; import { Logger } from 'winston'; import { getRootLogger } from '../logging'; -/** @public */ +/** + * Options passed to the {@link errorHandler} middleware. + * + * @public + */ export type ErrorHandlerOptions = { /** * Whether error response bodies should show error stack traces or not. diff --git a/packages/backend-common/src/middleware/statusCheckHandler.ts b/packages/backend-common/src/middleware/statusCheckHandler.ts index a0ba59fac4..4655d610fe 100644 --- a/packages/backend-common/src/middleware/statusCheckHandler.ts +++ b/packages/backend-common/src/middleware/statusCheckHandler.ts @@ -16,10 +16,19 @@ import { NextFunction, Request, Response, RequestHandler } from 'express'; -/** @public */ +/** + * A custom status checking function, passed to {@link statusCheckHandler} and + * {@link createStatusCheckRouter}. + * + * @public + */ export type StatusCheck = () => Promise; -/** @public */ +/** + * Options passed to {@link statusCheckHandler}. + * + * @public + */ export interface StatusCheckHandlerOptions { /** * Optional status function which returns a message. diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.ts b/packages/backend-common/src/reading/AwsS3UrlReader.ts index 1fca72e91c..e64334321d 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.ts @@ -94,6 +94,11 @@ const parseURL = ( }; }; +/** + * Implements a {@link UrlReader} for AWS S3 buckets. + * + * @public + */ export class AwsS3UrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index 37d97b8941..d6ceaec258 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -38,7 +38,11 @@ import { ReadUrlResponse, } from './types'; -/** @public */ +/** + * Implements a {@link UrlReader} for Azure repos. + * + * @public + */ export class AzureUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index b0e8056370..2006637545 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -41,8 +41,8 @@ import { } from './types'; /** - * A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as - * the one exposed by Bitbucket Cloud itself. + * Implements a {@link UrlReader} for files from Bitbucket v1 and v2 APIs, such + * as the one exposed by Bitbucket Cloud itself. * * @public */ diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 7090d26d35..cb873ebdf8 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -27,7 +27,7 @@ import { import path from 'path'; /** - * A UrlReader that does a plain fetch of the URL. + * A {@link UrlReader} that does a plain fetch of the URL. * * @public */ diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 6b18cfbe1a..72a8f0b90e 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -50,7 +50,7 @@ export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data']; /** - * A processor that adds the ability to read files from GitHub v3 APIs, such as + * Implements a {@link UrlReader} for files through the GitHub v3 APIs, such as * the one exposed by GitHub itself. * * @public diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 2444e317a7..d0e43fea59 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -39,7 +39,11 @@ import { } from './types'; import { trimEnd } from 'lodash'; -/** @public */ +/** + * Implements a {@link UrlReader} for files on GitLab. + * + * @public + */ export class GitlabUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts index f1684c2945..baa9477c6a 100644 --- a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts @@ -48,7 +48,11 @@ const parseURL = ( }; }; -/** @public */ +/** + * Implements a {@link UrlReader} for files on Google GCS. + * + * @public + */ export class GoogleGcsUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, logger }) => { if (!config.has('integrations.googleGcs')) { diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index a920ec080b..c0aae25808 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -27,7 +27,11 @@ import { FetchUrlReader } from './FetchUrlReader'; import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; -/** @public */ +/** + * Creation options for {@link UrlReaders}. + * + * @public + */ export type UrlReadersOptions = { /** Root config object */ config: Config; @@ -38,13 +42,13 @@ export type UrlReadersOptions = { }; /** - * UrlReaders provide various utilities related to the UrlReader interface. + * Helps construct {@link UrlReader}s. * * @public */ export class UrlReaders { /** - * Creates a UrlReader without any known types. + * Creates a custom {@link UrlReader} wrapper for your own set of factories. */ static create(options: UrlReadersOptions): UrlReader { const { logger, config, factories } = options; @@ -65,7 +69,8 @@ export class UrlReaders { } /** - * Creates a UrlReader that includes all the default factories from this package. + * Creates a {@link UrlReader} wrapper that includes all the default factories + * from this package. * * Any additional factories passed will be loaded before the default ones. */ diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index 46be7aa1b6..9505778705 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -281,7 +281,12 @@ export type FromReadableArrayOptions = Array<{ path: string; }>; -/** @public */ +/** + * A factory for response factories that handle the unpacking and inspection of + * complex responses such as archive data. + * + * @public + */ export interface ReadTreeResponseFactory { fromTarArchive( options: ReadTreeResponseFactoryOptions, diff --git a/packages/backend-common/src/scm/git.ts b/packages/backend-common/src/scm/git.ts index 0446f08767..9cf84e9872 100644 --- a/packages/backend-common/src/scm/git.ts +++ b/packages/backend-common/src/scm/git.ts @@ -33,7 +33,11 @@ From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub Azure 'notempty' token */ -/** @public */ +/** + * A convenience wrapper around the `isomorphic-git` library. + * + * @public + */ export class Git { private constructor( private readonly config: { diff --git a/packages/backend-common/src/service/createStatusCheckRouter.ts b/packages/backend-common/src/service/createStatusCheckRouter.ts index 0d0f93f25c..7c916da0e7 100644 --- a/packages/backend-common/src/service/createStatusCheckRouter.ts +++ b/packages/backend-common/src/service/createStatusCheckRouter.ts @@ -19,9 +19,26 @@ import Router from 'express-promise-router'; import express from 'express'; import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware'; -/** @public */ +/** + * Creates a default status checking router, that you can add to your express + * app. + * + * @remarks + * + * This adds a `/healthcheck` route (or any other path, if given as an + * argument), which your infra can call to see if the service is ready to serve + * requests. + * + * @public + */ export async function createStatusCheckRouter(options: { logger: Logger; + /** + * The path (including a leading slash) that the health check should be + * mounted on. + * + * @defaultValue '/healthcheck' + */ path?: string; /** * If not implemented, the default express middleware always returns 200. diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index 3e94196006..5df9f3fa5d 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -20,7 +20,11 @@ import { Router, RequestHandler, ErrorRequestHandler } from 'express'; import { Server } from 'http'; import { Logger } from 'winston'; -/** @public */ +/** + * A helper for building backend service instances. + * + * @public + */ export type ServiceBuilder = { /** * Sets the service parameters based on configuration. @@ -119,5 +123,9 @@ export type ServiceBuilder = { start(): Promise; }; -/** @public */ +/** + * A factory for request loggers. + * + * @public + */ export type RequestLoggingHandlerFactory = (logger?: Logger) => RequestHandler; diff --git a/packages/backend-common/src/util/ContainerRunner.ts b/packages/backend-common/src/util/ContainerRunner.ts index be861501c3..22e40ec455 100644 --- a/packages/backend-common/src/util/ContainerRunner.ts +++ b/packages/backend-common/src/util/ContainerRunner.ts @@ -16,7 +16,11 @@ import { Writable } from 'stream'; -/** @public */ +/** + * Options passed to the {@link ContainerRunner.runContainer} method. + * + * @public + */ export type RunContainerOptions = { imageName: string; command?: string | string[]; @@ -28,7 +32,14 @@ export type RunContainerOptions = { pullImage?: boolean; }; -/** @public */ +/** + * Handles the running of containers, on behalf of others. + * + * @public + */ export interface ContainerRunner { + /** + * Runs a container image to completion. + */ runContainer(opts: RunContainerOptions): Promise; } diff --git a/packages/backend-common/src/util/DockerContainerRunner.ts b/packages/backend-common/src/util/DockerContainerRunner.ts index 424913f316..522328c2ec 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.ts @@ -24,7 +24,11 @@ export type UserOptions = { User?: string; }; -/** @public */ +/** + * A {@link ContainerRunner} for Docker containers. + * + * @public + */ export class DockerContainerRunner implements ContainerRunner { private readonly dockerClient: Docker; From 8319c54e28fe9923062c721f1994971e127a5693 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:13:59 +0000 Subject: [PATCH 15/16] build(deps): bump @octokit/webhooks from 9.18.0 to 9.22.0 Bumps [@octokit/webhooks](https://github.com/octokit/webhooks.js) from 9.18.0 to 9.22.0. - [Release notes](https://github.com/octokit/webhooks.js/releases) - [Commits](https://github.com/octokit/webhooks.js/compare/v9.18.0...v9.22.0) --- updated-dependencies: - dependency-name: "@octokit/webhooks" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index e409fba60a..14c04bd646 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5209,19 +5209,19 @@ resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db" integrity sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig== -"@octokit/webhooks-types@4.15.0": - version "4.15.0" - resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-4.15.0.tgz#1158cba6578237d60957a37963a4a05654f5668b" - integrity sha512-s9LgKsUzq/JH3PWDjaD/m1DIlC/QWgBWbmXVqjdxJXJQBA67KZrLWjStVlYPf0mWlVZ1MOKphDyHiOGCbs0+Kg== +"@octokit/webhooks-types@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.2.0.tgz#9d1d451f37460107409c81cab04dd473108abb02" + integrity sha512-OZhKy1w8/GF4GWtdiJc+o8sloWAHRueGB78FWFLZnueK7EHV9MzDVr4weJZMflJwMK4uuYLzcnJVnAoy3yB35g== "@octokit/webhooks@^9.14.1": - version "9.18.0" - resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.18.0.tgz#19cc70e1ef281e33d830ea23e8011d25d8051f7f" - integrity sha512-N2hP7vCouKk9UWZxvqgWTPbp34i6g9Om/jk+TZeZ5Z+VsKjXvGtONlEd9H8DM1yOeEC+ARDpfhraX6UsK5tesQ== + version "9.22.0" + resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.22.0.tgz#07a36a10358d39c1870758fae2b1ad3c24ca578d" + integrity sha512-wUd7nGfDRHG6xkz311djmq6lIB2tQ+r94SNkyv9o0bQhOsrkwH8fQCM7uVsbpkGUU2lqCYsVoa8z/UC9HJgRaw== dependencies: "@octokit/request-error" "^2.0.2" "@octokit/webhooks-methods" "^2.0.0" - "@octokit/webhooks-types" "4.15.0" + "@octokit/webhooks-types" "5.2.0" aggregate-error "^3.1.0" "@open-draft/until@^1.0.3": From 06e2d79569667b8cd7b7e4be701d63ee52e099fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:16:23 +0000 Subject: [PATCH 16/16] build(deps): bump @microsoft/microsoft-graph-types from 2.8.0 to 2.11.0 Bumps [@microsoft/microsoft-graph-types](https://github.com/microsoftgraph/msgraph-typescript-typings) from 2.8.0 to 2.11.0. - [Release notes](https://github.com/microsoftgraph/msgraph-typescript-typings/releases) - [Commits](https://github.com/microsoftgraph/msgraph-typescript-typings/commits) --- updated-dependencies: - dependency-name: "@microsoft/microsoft-graph-types" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e409fba60a..15fc04072b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4745,9 +4745,9 @@ integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== "@microsoft/microsoft-graph-types@^2.6.0": - version "2.8.0" - resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.8.0.tgz#c3b538f99028e8609c5ebf95a494318a8f3d9201" - integrity sha512-NDgLn9IhYD/+nCeeGAi1JM7xTFqaM6rkXfLfiC1xvXy48BGBUrAf8fNFq5fkzBvGY8HfjzdPIkrJkfvLL+rzDQ== + version "2.11.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.11.0.tgz#0e1d3a0795855fc726e08836b1d3c4a72a8bcd00" + integrity sha512-v4Wuxp+kbcxeJGmb2UHbcukNr05XItFYXL+U3ReignI3Vl8tp1vfq0hkqP35Fun2QpqHJiu8Rkxj1MUF8d82ag== "@microsoft/tsdoc-config@~0.15.2": version "0.15.2"