From 744c0cbf9718b60a4ba300c25f59a412cb9ae9cc Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 20 Feb 2024 14:14:30 +0100 Subject: [PATCH] refactor(search-backend): use credential for authorized search engines Signed-off-by: Camila Belo --- .changeset/dirty-apes-divide.md | 6 ++ .changeset/odd-toys-wonder.md | 5 ++ .changeset/six-grapes-sniff.md | 5 ++ .changeset/violet-rocks-rescue.md | 5 ++ packages/backend/package.json | 1 - packages/backend/src/plugins/search.ts | 3 +- .../backend/src/plugins/search.ts.hbs | 1 + .../api-report.md | 2 +- .../src/engines/ElasticSearchSearchEngine.ts | 2 +- .../search-backend-module-pg/api-report.md | 2 +- .../src/PgSearchEngine/PgSearchEngine.ts | 2 +- .../search-backend-node/api-report-alpha.md | 29 +++++++- plugins/search-backend-node/api-report.md | 25 ++++++- .../src/IndexBuilder.test.ts | 3 +- .../search-backend-node/src/IndexBuilder.ts | 2 +- plugins/search-backend-node/src/alpha.ts | 12 ++-- .../src/engines/LunrSearchEngine.test.ts | 6 +- .../src/engines/LunrSearchEngine.ts | 3 +- plugins/search-backend-node/src/index.ts | 3 + plugins/search-backend-node/src/types.ts | 57 +++++++++++++++- plugins/search-backend/api-report.md | 8 ++- plugins/search-backend/src/alpha.ts | 19 +++++- .../service/AuthorizedSearchEngine.test.ts | 2 +- .../src/service/AuthorizedSearchEngine.ts | 10 +-- .../search-backend/src/service/router.test.ts | 32 +++++++-- plugins/search-backend/src/service/router.ts | 32 ++++++--- .../src/service/standaloneServer.ts | 1 + plugins/search-common/api-report.md | 6 +- plugins/search-common/src/deprecated.ts | 68 +++++++++++++++++++ plugins/search-common/src/index.ts | 1 + plugins/search-common/src/types.ts | 49 +------------ yarn.lock | 1 - 32 files changed, 308 insertions(+), 95 deletions(-) create mode 100644 .changeset/dirty-apes-divide.md create mode 100644 .changeset/odd-toys-wonder.md create mode 100644 .changeset/six-grapes-sniff.md create mode 100644 .changeset/violet-rocks-rescue.md create mode 100644 plugins/search-common/src/deprecated.ts diff --git a/.changeset/dirty-apes-divide.md b/.changeset/dirty-apes-divide.md new file mode 100644 index 0000000000..e90bc7f5e1 --- /dev/null +++ b/.changeset/dirty-apes-divide.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-pg': patch +--- + +Start importing `QueryTranslator`, `QueryRequestOptions` and `SearchEngine` from the `@backstage/plugin-search-backend-node`. diff --git a/.changeset/odd-toys-wonder.md b/.changeset/odd-toys-wonder.md new file mode 100644 index 0000000000..aedd915c6e --- /dev/null +++ b/.changeset/odd-toys-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-common': patch +--- + +Deprecate `QueryTranslator`, `QueryRequestOptions` and `SearchEngine` in favor of the types exported from `@backstage/plugin-search-backend-node`. diff --git a/.changeset/six-grapes-sniff.md b/.changeset/six-grapes-sniff.md new file mode 100644 index 0000000000..43e5ef04f6 --- /dev/null +++ b/.changeset/six-grapes-sniff.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +**BREAKING**: Update the router to use the new `auth` services. The router now requires a discovery service option to get credentials for the permission service. diff --git a/.changeset/violet-rocks-rescue.md b/.changeset/violet-rocks-rescue.md new file mode 100644 index 0000000000..91fbcdd871 --- /dev/null +++ b/.changeset/violet-rocks-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-node': patch +--- + +Exports `QueryTranslator`, `QueryRequestOptions` and `SearchEngine` types. These new types were extracted from the `@backstage/plugin-search-common` package and the `token` property was deprecated in favor of the a new credentials one. diff --git a/packages/backend/package.json b/packages/backend/package.json index e6102b69cf..e2b8c5d50f 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -72,7 +72,6 @@ "@backstage/plugin-search-backend-module-pg": "workspace:^", "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", - "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "@backstage/plugin-tech-insights-backend": "workspace:^", diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 7babfe7e1f..a5b976ec9d 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -23,9 +23,9 @@ import { ElasticSearchSearchEngine } from '@backstage/plugin-search-backend-modu import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg'; import { IndexBuilder, + SearchEngine, LunrSearchEngine, } from '@backstage/plugin-search-backend-node'; -import { SearchEngine } from '@backstage/plugin-search-common'; import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; @@ -117,6 +117,7 @@ export default async function createPlugin( return await createRouter({ engine: indexBuilder.getSearchEngine(), types: indexBuilder.getDocumentTypes(), + discovery: env.discovery, permissions: env.permissions, config: env.config, logger: env.logger, diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts.hbs b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts.hbs index 467ac60a5a..4149f67193 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts.hbs +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts.hbs @@ -60,6 +60,7 @@ export default async function createPlugin( engine: indexBuilder.getSearchEngine(), types: indexBuilder.getDocumentTypes(), permissions: env.permissions, + discovery: env.discovery, config: env.config, logger: env.logger, }); diff --git a/plugins/search-backend-module-elasticsearch/api-report.md b/plugins/search-backend-module-elasticsearch/api-report.md index 7de3a1857b..053c267f29 100644 --- a/plugins/search-backend-module-elasticsearch/api-report.md +++ b/plugins/search-backend-module-elasticsearch/api-report.md @@ -17,7 +17,7 @@ import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Readable } from 'stream'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery } from '@backstage/plugin-search-common'; import { TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport'; import { TransportRequestPromise as TransportRequestPromise_2 } from '@elastic/elasticsearch/lib/Transport'; diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index 8ccbe9a4ec..f8c996df51 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -18,9 +18,9 @@ import { IndexableDocument, IndexableResult, IndexableResultSet, - SearchEngine, SearchQuery, } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { isEmpty, isNumber, isNaN as nan } from 'lodash'; import { AwsSigv4Signer } from '@opensearch-project/opensearch/aws'; diff --git a/plugins/search-backend-module-pg/api-report.md b/plugins/search-backend-module-pg/api-report.md index 3c6096090e..21f2d7737d 100644 --- a/plugins/search-backend-module-pg/api-report.md +++ b/plugins/search-backend-module-pg/api-report.md @@ -10,7 +10,7 @@ import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery } from '@backstage/plugin-search-common'; // @public diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 1a27b9f981..b6513a0a97 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -15,7 +15,7 @@ */ import { PluginDatabaseManager } from '@backstage/backend-common'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery, IndexableResultSet, diff --git a/plugins/search-backend-node/api-report-alpha.md b/plugins/search-backend-node/api-report-alpha.md index 74b7a4b4bc..a7ac52a218 100644 --- a/plugins/search-backend-node/api-report-alpha.md +++ b/plugins/search-backend-node/api-report-alpha.md @@ -3,12 +3,39 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// + +import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { DocumentTypeInfo } from '@backstage/plugin-search-common'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { RegisterCollatorParameters } from '@backstage/plugin-search-backend-node'; import { RegisterDecoratorParameters } from '@backstage/plugin-search-backend-node'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchQuery } from '@backstage/plugin-search-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; +import { Writable } from 'stream'; + +// @public +export type QueryRequestOptions = + | { + token?: string; + } + | { + credentials: BackstageCredentials; + }; + +// @public +export type QueryTranslator = (query: SearchQuery) => unknown; + +// @public +export interface SearchEngine { + getIndexer(type: string): Promise; + query( + query: SearchQuery, + options?: QueryRequestOptions, + ): Promise; + setTranslator(translator: QueryTranslator): void; +} // @alpha export interface SearchEngineRegistryExtensionPoint { diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index 734bf6e1c0..4109b12cf5 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { DocumentDecoratorFactory } from '@backstage/plugin-search-common'; @@ -14,9 +15,7 @@ import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { default as lunr_2 } from 'lunr'; import { Permission } from '@backstage/plugin-permission-common'; -import { QueryTranslator } from '@backstage/plugin-search-common'; import { Readable } from 'stream'; -import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; import { TaskFunction } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -144,6 +143,18 @@ export type NewlineDelimitedJsonCollatorFactoryOptions = { visibilityPermission?: Permission; }; +// @public +export type QueryRequestOptions = + | { + token?: string; + } + | { + credentials: BackstageCredentials; + }; + +// @public +export type QueryTranslator = (query: SearchQuery) => unknown; + // @public export interface RegisterCollatorParameters { factory: DocumentCollatorFactory; @@ -170,6 +181,16 @@ export type ScheduleTaskParameters = { scheduledRunner: TaskRunner; }; +// @public +export interface SearchEngine { + getIndexer(type: string): Promise; + query( + query: SearchQuery, + options?: QueryRequestOptions, + ): Promise; + setTranslator(translator: QueryTranslator): void; +} + // @public export class TestPipeline { execute(): Promise; diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index 4a2760dde1..70d00f6974 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -19,11 +19,10 @@ import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; import { DocumentCollatorFactory, DocumentDecoratorFactory, - SearchEngine, } from '@backstage/plugin-search-common'; import { Readable, Transform } from 'stream'; import { IndexBuilder } from './IndexBuilder'; -import { LunrSearchEngine } from './index'; +import { LunrSearchEngine, SearchEngine } from './index'; class TestDocumentCollatorFactory implements DocumentCollatorFactory { readonly type: string = 'anything'; diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 907519f289..98c6853cb1 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -17,12 +17,12 @@ import { DocumentDecoratorFactory, DocumentTypeInfo, - SearchEngine, } from '@backstage/plugin-search-common'; import { Transform, pipeline } from 'stream'; import { Logger } from 'winston'; import { Scheduler } from './Scheduler'; import { + SearchEngine, IndexBuilderOptions, RegisterCollatorParameters, RegisterDecoratorParameters, diff --git a/plugins/search-backend-node/src/alpha.ts b/plugins/search-backend-node/src/alpha.ts index 943e11b580..a491e158b4 100644 --- a/plugins/search-backend-node/src/alpha.ts +++ b/plugins/search-backend-node/src/alpha.ts @@ -22,10 +22,7 @@ import { coreServices, } from '@backstage/backend-plugin-api'; import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { - DocumentTypeInfo, - SearchEngine, -} from '@backstage/plugin-search-common'; +import { DocumentTypeInfo } from '@backstage/plugin-search-common'; import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { @@ -33,8 +30,15 @@ import { RegisterDecoratorParameters, } from '@backstage/plugin-search-backend-node'; +import { SearchEngine } from './types'; import { IndexBuilder } from './IndexBuilder'; +export type { + SearchEngine, + QueryRequestOptions, + QueryTranslator, +} from './types'; + /** * @alpha * Options for build method on {@link SearchIndexService}. diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 55c18cd6fc..e5347b3cb1 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -16,10 +16,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import lunr from 'lunr'; -import { - IndexableDocument, - SearchEngine, -} from '@backstage/plugin-search-common'; +import { IndexableDocument } from '@backstage/plugin-search-common'; import { ConcreteLunrQuery, LunrSearchEngine, @@ -28,6 +25,7 @@ import { parseHighlightFields, } from './LunrSearchEngine'; import { LunrSearchEngineIndexer } from './LunrSearchEngineIndexer'; +import { SearchEngine } from '../types'; import { TestPipeline } from '../test-utils'; /** diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index 27cfae3fdf..a2db52f960 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -18,9 +18,8 @@ import { IndexableDocument, IndexableResultSet, SearchQuery, - QueryTranslator, - SearchEngine, } from '@backstage/plugin-search-common'; +import { SearchEngine, QueryTranslator } from '../types'; import { MissingIndexError } from '../errors'; import lunr from 'lunr'; import { v4 as uuid } from 'uuid'; diff --git a/plugins/search-backend-node/src/index.ts b/plugins/search-backend-node/src/index.ts index a188509c1c..78e4880305 100644 --- a/plugins/search-backend-node/src/index.ts +++ b/plugins/search-backend-node/src/index.ts @@ -33,6 +33,9 @@ export type { IndexBuilderOptions, RegisterCollatorParameters, RegisterDecoratorParameters, + SearchEngine, + QueryRequestOptions, + QueryTranslator, } from './types'; export * from './errors'; export * from './indexing'; diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index dfcf4d12ce..742d575ed3 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -14,12 +14,15 @@ * limitations under the License. */ +import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskRunner } from '@backstage/backend-tasks'; import { DocumentCollatorFactory, DocumentDecoratorFactory, - SearchEngine, + IndexableResultSet, + SearchQuery, } from '@backstage/plugin-search-common'; +import { Writable } from 'stream'; import { Logger } from 'winston'; /** @@ -57,3 +60,55 @@ export interface RegisterDecoratorParameters { */ factory: DocumentDecoratorFactory; } + +/** + * A type of function responsible for translating an abstract search query into + * a concrete query relevant to a particular search engine. + * @public + */ +export type QueryTranslator = (query: SearchQuery) => unknown; + +/** + * Options when querying a search engine. + * @public + */ +export type QueryRequestOptions = + | { + /** @deprecated use the `credentials` option instead. */ + token?: string; + } + | { + credentials: BackstageCredentials; + }; + +/** + * Interface that must be implemented by specific search engines, responsible + * for performing indexing and querying and translating abstract queries into + * concrete, search engine-specific queries. + * @public + */ +export interface SearchEngine { + /** + * Override the default translator provided by the SearchEngine. + */ + setTranslator(translator: QueryTranslator): void; + + /** + * Factory method for getting a search engine indexer for a given document + * type. + * + * @param type - The type or name of the document set for which an indexer + * should be retrieved. This corresponds to the `type` property on the + * document collator/decorator factories and will most often be used to + * identify an index or group to which documents should be written. + */ + getIndexer(type: string): Promise; + + /** + * Perform a search query against the SearchEngine. + */ + query( + query: SearchQuery, + options?: QueryRequestOptions, + ): Promise; +} diff --git a/plugins/search-backend/api-report.md b/plugins/search-backend/api-report.md index d6274d9a94..f868ed43a3 100644 --- a/plugins/search-backend/api-report.md +++ b/plugins/search-backend/api-report.md @@ -3,13 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import { DocumentTypeInfo } from '@backstage/plugin-search-common'; import express from 'express'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; // @public (undocumented) export function createRouter(options: RouterOptions): Promise; @@ -18,8 +21,11 @@ export function createRouter(options: RouterOptions): Promise; export type RouterOptions = { engine: SearchEngine; types: Record; + discovery: DiscoveryService; permissions: PermissionEvaluator | PermissionAuthorizer; config: Config; logger: Logger; + auth?: AuthService; + httpAuth?: HttpAuthService; }; ``` diff --git a/plugins/search-backend/src/alpha.ts b/plugins/search-backend/src/alpha.ts index 8a9d4498bc..86bbcdeebe 100644 --- a/plugins/search-backend/src/alpha.ts +++ b/plugins/search-backend/src/alpha.ts @@ -22,6 +22,7 @@ import { loggerToWinstonLogger } from '@backstage/backend-common'; import { RegisterCollatorParameters, RegisterDecoratorParameters, + SearchEngine, LunrSearchEngine, } from '@backstage/plugin-search-backend-node'; import { @@ -33,7 +34,6 @@ import { } from '@backstage/plugin-search-backend-node/alpha'; import { createRouter } from './service/router'; -import { SearchEngine } from '@backstage/plugin-search-common'; class SearchIndexRegistry implements SearchIndexRegistryExtensionPoint { private collators: RegisterCollatorParameters[] = []; @@ -94,11 +94,23 @@ export default createBackendPlugin({ deps: { logger: coreServices.logger, config: coreServices.rootConfig, + discovery: coreServices.discovery, permissions: coreServices.permissions, + auth: coreServices.auth, http: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, searchIndexService: searchIndexServiceRef, }, - async init({ config, logger, permissions, http, searchIndexService }) { + async init({ + config, + logger, + discovery, + permissions, + auth, + http, + httpAuth, + searchIndexService, + }) { let searchEngine = searchEngineRegistry.getSearchEngine(); if (!searchEngine) { searchEngine = new LunrSearchEngine({ @@ -117,7 +129,10 @@ export default createBackendPlugin({ const router = await createRouter({ config, + discovery, permissions, + auth, + httpAuth, logger: loggerToWinstonLogger(logger), engine: searchEngine, types: searchIndexService.getDocumentTypes(), diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts index b2355a7239..420af5744b 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts @@ -25,8 +25,8 @@ import { import { DocumentTypeInfo, IndexableDocument, - SearchEngine, } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { encodePageCursor, decodePageCursor, diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index 8d285d053e..defb535f42 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -23,21 +23,23 @@ import { EvaluatePermissionRequest, EvaluatePermissionResponse, isResourcePermission, - PermissionEvaluator, QueryPermissionRequest, } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, IndexableResult, IndexableResultSet, + SearchQuery, +} from '@backstage/plugin-search-common'; +import { QueryRequestOptions, QueryTranslator, SearchEngine, - SearchQuery, -} from '@backstage/plugin-search-common'; +} from '@backstage/plugin-search-backend-node'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { Writable } from 'stream'; +import { PermissionsService } from '@backstage/backend-plugin-api'; export function decodePageCursor(pageCursor?: string): { page: number } { if (!pageCursor) { @@ -68,7 +70,7 @@ export class AuthorizedSearchEngine implements SearchEngine { constructor( private readonly searchEngine: SearchEngine, private readonly types: Record, - private readonly permissions: PermissionEvaluator, + private readonly permissions: PermissionsService, config: Config, ) { this.queryLatencyBudgetMs = diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 401488dcbf..7c89a8c750 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -14,16 +14,22 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + getVoidLogger, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { IndexBuilder } from '@backstage/plugin-search-backend-node'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { + IndexBuilder, + SearchEngine, +} from '@backstage/plugin-search-backend-node'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils'; import { Server } from 'http'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; const mockPermissionEvaluator: PermissionEvaluator = { authorize: () => { @@ -38,6 +44,16 @@ describe('createRouter', () => { let app: express.Express | Server; let mockSearchEngine: jest.Mocked; + const mockBaseUrl = 'http://backstage:9191/api/proxy'; + const discovery: PluginEndpointDiscovery = { + async getBaseUrl() { + return mockBaseUrl; + }, + async getExternalBaseUrl() { + return mockBaseUrl; + }, + }; + beforeAll(async () => { const logger = getVoidLogger(); mockSearchEngine = { @@ -65,7 +81,10 @@ describe('createRouter', () => { search: { maxPageLimit: 200, maxTermLength: 20 }, }), permissions: mockPermissionEvaluator, + discovery, logger, + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), }); app = wrapInOpenApiTestServer(express().use(router)); }); @@ -227,7 +246,11 @@ describe('createRouter', () => { unknownKey2: 'unknownValue1', }; const secondArg = { - token: undefined, + credentials: mockCredentials.user(), + token: mockCredentials.service.token({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'search', + }), }; expect(response.status).toEqual(200); expect(mockSearchEngine.query).toHaveBeenCalledWith(firstArg, secondArg); @@ -251,6 +274,7 @@ describe('createRouter', () => { types: indexBuilder.getDocumentTypes(), config: new ConfigReader({ permissions: { enabled: false } }), permissions: mockPermissionEvaluator, + discovery, logger, }); app = express().use(router); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 876cb5d103..9f811d5d06 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -17,11 +17,13 @@ import express from 'express'; import { Logger } from 'winston'; import { z } from 'zod'; -import { errorHandler } from '@backstage/backend-common'; +import { + createLegacyAuthAdapters, + errorHandler, +} from '@backstage/backend-common'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { JsonObject, JsonValue } from '@backstage/types'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; import { PermissionAuthorizer, PermissionEvaluator, @@ -32,9 +34,14 @@ import { IndexableResultSet, SearchResultSet, } from '@backstage/plugin-search-common'; -import { SearchEngine } from '@backstage/plugin-search-common'; +import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { AuthorizedSearchEngine } from './AuthorizedSearchEngine'; import { createOpenApiRouter } from '../schema/openapi.generated'; +import { + AuthService, + DiscoveryService, + HttpAuthService, +} from '@backstage/backend-plugin-api'; const jsonObjectSchema: z.ZodSchema = z.lazy(() => { const jsonValueSchema: z.ZodSchema = z.lazy(() => @@ -57,9 +64,12 @@ const jsonObjectSchema: z.ZodSchema = z.lazy(() => { export type RouterOptions = { engine: SearchEngine; types: Record; + discovery: DiscoveryService; permissions: PermissionEvaluator | PermissionAuthorizer; config: Config; logger: Logger; + auth?: AuthService; + httpAuth?: HttpAuthService; }; const defaultMaxPageLimit = 100; @@ -75,6 +85,8 @@ export async function createRouter( const router = await createOpenApiRouter(); const { engine: inputEngine, types, permissions, config, logger } = options; + const { auth, httpAuth } = createLegacyAuthAdapters(options); + const maxPageLimit = config.getOptionalNumber('search.maxPageLimit') ?? defaultMaxPageLimit; @@ -169,12 +181,16 @@ export async function createRouter( }`, ); - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); - try { - const resultSet = await engine?.query(query, { token }); + const credentials = await httpAuth.credentials(req); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'search', + }); + const resultSet = await engine?.query(query, { + token, + credentials, + }); res.json(filterResultSet(toSearchResults(resultSet))); } catch (error) { diff --git a/plugins/search-backend/src/service/standaloneServer.ts b/plugins/search-backend/src/service/standaloneServer.ts index 9e4294d39f..09fa4f87ee 100644 --- a/plugins/search-backend/src/service/standaloneServer.ts +++ b/plugins/search-backend/src/service/standaloneServer.ts @@ -57,6 +57,7 @@ export async function startStandaloneServer( const router = await createRouter({ engine: indexBuilder.getSearchEngine(), types: indexBuilder.getDocumentTypes(), + discovery, permissions, config, logger, diff --git a/plugins/search-common/api-report.md b/plugins/search-common/api-report.md index 2006689ab9..497defc46d 100644 --- a/plugins/search-common/api-report.md +++ b/plugins/search-common/api-report.md @@ -42,12 +42,12 @@ export type IndexableResult = Result; // @public (undocumented) export type IndexableResultSet = ResultSet; -// @public +// @public @deprecated export type QueryRequestOptions = { token?: string; }; -// @public +// @public @deprecated export type QueryTranslator = (query: SearchQuery) => unknown; // @public (undocumented) @@ -87,7 +87,7 @@ export interface SearchDocument { title: string; } -// @public +// @public @deprecated export interface SearchEngine { getIndexer(type: string): Promise; query( diff --git a/plugins/search-common/src/deprecated.ts b/plugins/search-common/src/deprecated.ts new file mode 100644 index 0000000000..8d76d3a22d --- /dev/null +++ b/plugins/search-common/src/deprecated.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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 { Writable } from 'stream'; +import { SearchQuery, IndexableResultSet } from './types'; + +/** + * A type of function responsible for translating an abstract search query into + * a concrete query relevant to a particular search engine. + * @public + * @deprecated Import from `@backstage/plugin-search-backend-node` instead + */ +export type QueryTranslator = (query: SearchQuery) => unknown; + +/** + * Options when querying a search engine. + * @public + * @deprecated Import from `@backstage/plugin-search-backend-node` instead + */ +export type QueryRequestOptions = { + token?: string; +}; + +/** + * Interface that must be implemented by specific search engines, responsible + * for performing indexing and querying and translating abstract queries into + * concrete, search engine-specific queries. + * @public + * @deprecated Import from `@backstage/plugin-search-backend-node` instead + */ +export interface SearchEngine { + /** + * Override the default translator provided by the SearchEngine. + */ + setTranslator(translator: QueryTranslator): void; + + /** + * Factory method for getting a search engine indexer for a given document + * type. + * + * @param type - The type or name of the document set for which an indexer + * should be retrieved. This corresponds to the `type` property on the + * document collator/decorator factories and will most often be used to + * identify an index or group to which documents should be written. + */ + getIndexer(type: string): Promise; + + /** + * Perform a search query against the SearchEngine. + */ + query( + query: SearchQuery, + options?: QueryRequestOptions, + ): Promise; +} diff --git a/plugins/search-common/src/index.ts b/plugins/search-common/src/index.ts index e71f8cd660..5d8ae2d42f 100644 --- a/plugins/search-common/src/index.ts +++ b/plugins/search-common/src/index.ts @@ -21,3 +21,4 @@ */ export * from './types'; +export * from './deprecated'; diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 2ea4a490ca..9d1f9aa887 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -16,7 +16,7 @@ import { Permission } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; -import { Readable, Transform, Writable } from 'stream'; +import { Readable, Transform } from 'stream'; /** * @public @@ -206,50 +206,3 @@ export interface DocumentDecoratorFactory { */ getDecorator(): Promise; } - -/** - * A type of function responsible for translating an abstract search query into - * a concrete query relevant to a particular search engine. - * @public - */ -export type QueryTranslator = (query: SearchQuery) => unknown; - -/** - * Options when querying a search engine. - * @public - */ -export type QueryRequestOptions = { - token?: string; -}; - -/** - * Interface that must be implemented by specific search engines, responsible - * for performing indexing and querying and translating abstract queries into - * concrete, search engine-specific queries. - * @public - */ -export interface SearchEngine { - /** - * Override the default translator provided by the SearchEngine. - */ - setTranslator(translator: QueryTranslator): void; - - /** - * Factory method for getting a search engine indexer for a given document - * type. - * - * @param type - The type or name of the document set for which an indexer - * should be retrieved. This corresponds to the `type` property on the - * document collator/decorator factories and will most often be used to - * identify an index or group to which documents should be written. - */ - getIndexer(type: string): Promise; - - /** - * Perform a search query against the SearchEngine. - */ - query( - query: SearchQuery, - options?: QueryRequestOptions, - ): Promise; -} diff --git a/yarn.lock b/yarn.lock index 5aa82df2e2..6f5818920a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27469,7 +27469,6 @@ __metadata: "@backstage/plugin-search-backend-module-pg": "workspace:^" "@backstage/plugin-search-backend-module-techdocs": "workspace:^" "@backstage/plugin-search-backend-node": "workspace:^" - "@backstage/plugin-search-common": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-signals-node": "workspace:^" "@backstage/plugin-tech-insights-backend": "workspace:^"