refactor(search-backend): use credential for authorized search engines
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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<express.Router>;
|
||||
@@ -18,8 +21,11 @@ export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
export type RouterOptions = {
|
||||
engine: SearchEngine;
|
||||
types: Record<string, DocumentTypeInfo>;
|
||||
discovery: DiscoveryService;
|
||||
permissions: PermissionEvaluator | PermissionAuthorizer;
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
auth?: AuthService;
|
||||
httpAuth?: HttpAuthService;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string, DocumentTypeInfo>,
|
||||
private readonly permissions: PermissionEvaluator,
|
||||
private readonly permissions: PermissionsService,
|
||||
config: Config,
|
||||
) {
|
||||
this.queryLatencyBudgetMs =
|
||||
|
||||
@@ -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<SearchEngine>;
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<JsonObject> = z.lazy(() => {
|
||||
const jsonValueSchema: z.ZodSchema<JsonValue> = z.lazy(() =>
|
||||
@@ -57,9 +64,12 @@ const jsonObjectSchema: z.ZodSchema<JsonObject> = z.lazy(() => {
|
||||
export type RouterOptions = {
|
||||
engine: SearchEngine;
|
||||
types: Record<string, DocumentTypeInfo>;
|
||||
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) {
|
||||
|
||||
@@ -57,6 +57,7 @@ export async function startStandaloneServer(
|
||||
const router = await createRouter({
|
||||
engine: indexBuilder.getSearchEngine(),
|
||||
types: indexBuilder.getDocumentTypes(),
|
||||
discovery,
|
||||
permissions,
|
||||
config,
|
||||
logger,
|
||||
|
||||
Reference in New Issue
Block a user