diff --git a/.changeset/eight-dancers-burn.md b/.changeset/eight-dancers-burn.md new file mode 100644 index 0000000000..228ff036ab --- /dev/null +++ b/.changeset/eight-dancers-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-common': minor +--- + +**BREAKING**: Removed the deprecated and unused `token` option from `EvaluatorRequestOptions`. The `PermissionsClient` now has its own `PermissionClientRequestOptions` type that declares the `token` option instead. diff --git a/.changeset/gold-worms-complain.md b/.changeset/gold-worms-complain.md new file mode 100644 index 0000000000..229293cc2f --- /dev/null +++ b/.changeset/gold-worms-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': minor +--- + +**BREAKING**: Updated the `ServerPermissionClient` to match the new `PermissionsService` interface, where the deprecated `token` option has been removed and the options are now required. diff --git a/.changeset/swift-seals-drive.md b/.changeset/swift-seals-drive.md new file mode 100644 index 0000000000..a03f490d3f --- /dev/null +++ b/.changeset/swift-seals-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: The `PermissionsService` no longer supports passing the deprecated `token` option, and the request options are now required. diff --git a/.changeset/tiny-pianos-work.md b/.changeset/tiny-pianos-work.md new file mode 100644 index 0000000000..033e8272e5 --- /dev/null +++ b/.changeset/tiny-pianos-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +The `AuthorizedSearchEngine` will now ignore the deprecated `token` option, and treat it as an unauthorized request. This will not have any effect in practice, since credentials are always provided by the router. diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 7b1e6ef2d3..1937ba6ca9 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -9,6 +9,7 @@ import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common' import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; import { Duration } from 'luxon'; +import { EvaluatorRequestOptions } from '@backstage/plugin-permission-common'; import { Handler } from 'express'; import { HumanDuration } from '@backstage/types'; import { IdentityApi } from '@backstage/plugin-auth-node'; @@ -423,22 +424,20 @@ export interface LoggerService { export interface PermissionsService extends PermissionEvaluator { authorize( requests: AuthorizePermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; authorizeConditional( requests: QueryPermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; } // @public -export type PermissionsServiceRequestOptions = - | { - token?: string; - } - | { - credentials: BackstageCredentials; - }; +export interface PermissionsServiceRequestOptions + extends EvaluatorRequestOptions { + // (undocumented) + credentials: BackstageCredentials; +} // @public export interface PluginMetadataService { diff --git a/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts b/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts index c5046a988b..a25081d17a 100644 --- a/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts +++ b/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts @@ -17,6 +17,7 @@ import { AuthorizePermissionRequest, AuthorizePermissionResponse, + EvaluatorRequestOptions, PermissionEvaluator, QueryPermissionRequest, QueryPermissionResponse, @@ -24,18 +25,14 @@ import { import { BackstageCredentials } from './AuthService'; /** - * Options for {@link @backstage/plugin-permission-common#PermissionEvaluator} requests. + * Options for {@link PermissionsService} requests. * * @public */ -export type PermissionsServiceRequestOptions = - | { - /** @deprecated use the `credentials` option instead. */ - token?: string; - } - | { - credentials: BackstageCredentials; - }; +export interface PermissionsServiceRequestOptions + extends EvaluatorRequestOptions { + credentials: BackstageCredentials; +} /** * Permission system integration for authorization of user/service actions. @@ -59,7 +56,7 @@ export interface PermissionsService extends PermissionEvaluator { */ authorize( requests: AuthorizePermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; /** @@ -79,6 +76,6 @@ export interface PermissionsService extends PermissionEvaluator { */ authorizeConditional( requests: QueryPermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; } diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index 1c2476d20d..e9935c612d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -94,9 +94,7 @@ export type EvaluatePermissionResponseBatch = PermissionMessageBatch; // @public -export type EvaluatorRequestOptions = { - token?: string; -}; +export interface EvaluatorRequestOptions {} // @public export type IdentifiedPermissionMessage = T & { @@ -162,14 +160,19 @@ export class PermissionClient implements PermissionEvaluator { constructor(options: { discovery: DiscoveryApi; config: Config }); authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise; authorizeConditional( queries: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise; } +// @public +export type PermissionClientRequestOptions = { + token?: string; +}; + // @public export type PermissionCondition< TResourceType extends string = string, @@ -191,11 +194,15 @@ export type PermissionCriteria = export interface PermissionEvaluator { authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { + _ignored?: never; + }, ): Promise; authorizeConditional( requests: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { + _ignored?: never; + }, ): Promise; } diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index a5d08205d8..2414774775 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -27,7 +27,6 @@ import { PermissionEvaluator, QueryPermissionRequest, AuthorizePermissionRequest, - EvaluatorRequestOptions, AuthorizePermissionResponse, QueryPermissionResponse, } from './types/api'; @@ -93,6 +92,15 @@ const responseSchema = ( ), }); +/** + * Options for {@link PermissionClient} requests. + * + * @public + */ +export type PermissionClientRequestOptions = { + token?: string; +}; + /** * An isomorphic client for requesting authorization for Backstage permissions. * @public @@ -112,7 +120,7 @@ export class PermissionClient implements PermissionEvaluator { */ async authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise { return this.makeRequest( requests, @@ -126,7 +134,7 @@ export class PermissionClient implements PermissionEvaluator { */ async authorizeConditional( queries: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise { return this.makeRequest(queries, queryPermissionResponseSchema, options); } diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index e597384a56..072615989b 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -244,7 +244,7 @@ export interface PermissionEvaluator { */ authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options ): Promise; /** @@ -257,22 +257,20 @@ export interface PermissionEvaluator { */ authorizeConditional( requests: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options ): Promise; } +// Note(Rugvip): I kept the below type around in case we want to add new options +// in the future, for example a signal. It also helps out enabling API +// constraints, as without this we can't have the permissions service implement +// the evaluator interface due to the mismatch in parameter count. + /** * Options for {@link PermissionEvaluator} requests. * + * This is currently empty, as there are no longer any common options for the permission evaluator. + * * @public */ -export type EvaluatorRequestOptions = { - /** - * @deprecated Backend plugins should no longer depend on the - * `PermissionEvaluator`, but instead use the `PermissionService` from - * `@backstage/backend-plugin-api`. Frontend plugins should not need to inject - * this token at all, but instead implicitly rely on underlying fetchApi to do - * it for them. - */ - token?: string; -}; +export interface EvaluatorRequestOptions {} diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 6e99ff71ab..028517c5e0 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -83,18 +83,22 @@ describe('ServerPermissionClient', () => { const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { discovery, tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), }); - await client.authorize([ - { - permission: testBasicPermission, - }, - ]); + await client.authorize( + [ + { + permission: testBasicPermission, + }, + ], + { credentials: mockCredentials.none() }, + ); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { + it('should bypass the permission backend if permissions are enabled and request has valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), @@ -102,13 +106,13 @@ describe('ServerPermissionClient', () => { }); await client.authorize([{ permission: testBasicPermission }], { - token: mockCredentials.service.token(), + credentials: mockCredentials.service(), }); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { + it('should call the permission backend if permissions are enabled and request does not have valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), @@ -116,10 +120,18 @@ describe('ServerPermissionClient', () => { }); await client.authorize([{ permission: testBasicPermission }], { - token: mockCredentials.user.token(), + credentials: mockCredentials.user(), }); expect(mockAuthorizeHandler).toHaveBeenCalled(); + expect( + mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), + ).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'permission', + }), + ); }); }); @@ -145,201 +157,59 @@ describe('ServerPermissionClient', () => { const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { discovery, tokenManager: mockServices.tokenManager.mock(), - }); - - await client.authorizeConditional([ - { permission: testResourcePermission }, - ]); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), }); await client.authorizeConditional( [{ permission: testResourcePermission }], { - token: mockCredentials.service.token(), + credentials: mockCredentials.none(), }, ); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const auth = mockServices.auth(); + it('should bypass the permission backend if permissions are enabled and request has valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), - auth, + auth: mockServices.auth(), }); await client.authorizeConditional( [{ permission: testResourcePermission }], { - token: mockCredentials.user.token(), + credentials: mockCredentials.service(), + }, + ); + + expect(mockAuthorizeHandler).not.toHaveBeenCalled(); + }); + + it('should call the permission backend if permissions are enabled and request does not have valid server credentials', async () => { + const client = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), + }); + + await client.authorizeConditional( + [{ permission: testResourcePermission }], + { + credentials: mockCredentials.user(), }, ); expect(mockAuthorizeHandler).toHaveBeenCalled(); - }); - }); - - describe('with credentials', () => { - describe('authorize', () => { - let mockAuthorizeHandler: jest.Mock; - - beforeEach(() => { - mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map( - (r: IdentifiedPermissionMessage) => ({ - id: r.id, - result: AuthorizeResult.ALLOW, - }), - ); - - return res(json({ items: responses })); - }); - - server.use(rest.post(`${mockBaseUrl}/authorize`, mockAuthorizeHandler)); - }); - - it('should bypass the permission backend if permissions are disabled', async () => { - const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize( - [ - { - permission: testBasicPermission, - }, - ], - { credentials: mockCredentials.none() }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service(), - }); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.user(), - }); - - expect(mockAuthorizeHandler).toHaveBeenCalled(); - expect( - mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), - ).toBe( - mockCredentials.service.header({ - onBehalfOf: mockCredentials.user(), - targetPluginId: 'permission', - }), - ); - }); - }); - - describe('authorizeConditional', () => { - let mockAuthorizeHandler: jest.Mock; - - beforeEach(() => { - mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map( - (r: IdentifiedPermissionMessage) => ({ - id: r.id, - result: AuthorizeResult.ALLOW, - }), - ); - - return res(json({ items: responses })); - }); - - server.use(rest.post(`${mockBaseUrl}/authorize`, mockAuthorizeHandler)); - }); - - it('should bypass the permission backend if permissions are disabled', async () => { - const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.none(), - }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.service(), - }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.user(), - }, - ); - - expect(mockAuthorizeHandler).toHaveBeenCalled(); - expect( - mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), - ).toBe( - mockCredentials.service.header({ - onBehalfOf: mockCredentials.user(), - targetPluginId: 'permission', - }), - ); - }); + expect( + mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), + ).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'permission', + }), + ); }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index cf54f3b894..e21067bfa1 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -146,14 +146,6 @@ export class ServerPermissionClient implements PermissionsService { return options.credentials; } - if (options?.token) { - try { - return await this.#auth.authenticate(options.token); - } catch { - // ignore - } - } - return undefined; } diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts index 420af5744b..e41f3f888b 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts @@ -32,6 +32,7 @@ import { decodePageCursor, AuthorizedSearchEngine, } from './AuthorizedSearchEngine'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; describe('AuthorizedSearchEngine', () => { const typeUsers = 'users'; @@ -117,10 +118,11 @@ describe('AuthorizedSearchEngine', () => { searchEngine, defaultTypes, permissionEvaluator, + mockServices.auth(), new ConfigReader({}), ); - const options = { token: 'token' }; + const options = { credentials: mockCredentials.user() }; const allowAll: PermissionEvaluator['authorize'] & PermissionEvaluator['authorizeConditional'] = async queries => { @@ -149,7 +151,7 @@ describe('AuthorizedSearchEngine', () => { types: ['one', 'two'], filters, }, - { token: 'token' }, + options, ); }); @@ -160,7 +162,18 @@ describe('AuthorizedSearchEngine', () => { await authorizedSearchEngine.query({ term: '' }, options); expect(mockedQuery).toHaveBeenCalledWith( { term: '', types: ['users', 'templates', 'services', 'groups'] }, - { token: 'token' }, + options, + ); + }); + + it('should fall back to none credentials if a token is provided', async () => { + mockedQuery.mockImplementation(async () => ({ results })); + mockedPermissionQuery.mockImplementation(allowAll); + + await authorizedSearchEngine.query({ term: '' }, { token: 'token' }); + expect(mockedQuery).toHaveBeenCalledWith( + { term: '', types: ['users', 'templates', 'services', 'groups'] }, + { credentials: mockCredentials.none() }, ); }); @@ -184,7 +197,7 @@ describe('AuthorizedSearchEngine', () => { ); expect(mockedQuery).toHaveBeenCalledWith( { term: '', types: ['users', 'templates'] }, - { token: 'token' }, + options, ); expect(mockedPermissionQuery).toHaveBeenCalledTimes(1); expect(mockedPermissionQuery).toHaveBeenLastCalledWith( @@ -192,7 +205,7 @@ describe('AuthorizedSearchEngine', () => { { permission: defaultTypes[typeUsers].visibilityPermission }, { permission: defaultTypes[typeTemplates].visibilityPermission }, ], - { token: 'token' }, + options, ); }); @@ -218,7 +231,7 @@ describe('AuthorizedSearchEngine', () => { expect(mockedQuery).toHaveBeenCalledWith( { term: '', types: ['templates', 'services', 'groups'] }, - { token: 'token' }, + options, ); expect(mockedPermissionQuery).toHaveBeenCalledTimes(1); @@ -270,7 +283,7 @@ describe('AuthorizedSearchEngine', () => { expect(mockedQuery).toHaveBeenCalledWith( { term: '', types: ['users'] }, - { token: 'token' }, + options, ); }); @@ -326,7 +339,7 @@ describe('AuthorizedSearchEngine', () => { }), }, ], - { token: 'token' }, + options, ); expect(mockedAuthorize).toHaveBeenCalledTimes(1); expect(mockedAuthorize).toHaveBeenCalledWith( @@ -338,7 +351,7 @@ describe('AuthorizedSearchEngine', () => { resourceRef: 'template_doc_0', }, ], - { token: 'token' }, + options, ); }); @@ -403,7 +416,7 @@ describe('AuthorizedSearchEngine', () => { expect(mockedQuery).toHaveBeenNthCalledWith( 1, { term: '', types: ['users', 'templates', 'services'] }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 2, @@ -412,7 +425,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services'], pageCursor: 'MQ==', }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 3, @@ -421,7 +434,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services'], pageCursor: 'Mg==', }, - { token: 'token' }, + options, ); const expectedResult = allDocuments @@ -494,7 +507,7 @@ describe('AuthorizedSearchEngine', () => { expect(mockedQuery).toHaveBeenNthCalledWith( 1, { term: '', types: ['users', 'templates', 'services', 'groups'] }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 2, @@ -503,7 +516,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services', 'groups'], pageCursor: 'MQ==', }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 3, @@ -512,7 +525,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services', 'groups'], pageCursor: 'Mg==', }, - { token: 'token' }, + options, ); const expectedResult = allDocuments @@ -574,7 +587,7 @@ describe('AuthorizedSearchEngine', () => { expect(mockedQuery).toHaveBeenNthCalledWith( 1, { term: '', types: ['users', 'templates', 'services'] }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 2, @@ -583,7 +596,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services'], pageCursor: 'MQ==', }, - { token: 'token' }, + options, ); expect(mockedQuery).toHaveBeenNthCalledWith( 3, @@ -592,7 +605,7 @@ describe('AuthorizedSearchEngine', () => { types: ['users', 'templates', 'services'], pageCursor: 'Mg==', }, - { token: 'token' }, + options, ); const expectedResults = servicesWithAuth diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index defb535f42..d9d3feeacf 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -39,7 +39,7 @@ import { import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { Writable } from 'stream'; -import { PermissionsService } from '@backstage/backend-plugin-api'; +import { AuthService, PermissionsService } from '@backstage/backend-plugin-api'; export function decodePageCursor(pageCursor?: string): { page: number } { if (!pageCursor) { @@ -71,6 +71,7 @@ export class AuthorizedSearchEngine implements SearchEngine { private readonly searchEngine: SearchEngine, private readonly types: Record, private readonly permissions: PermissionsService, + private readonly auth: AuthService, config: Config, ) { this.queryLatencyBudgetMs = @@ -92,9 +93,14 @@ export class AuthorizedSearchEngine implements SearchEngine { ): Promise { const queryStartTime = Date.now(); + const compatOptions = + 'credentials' in options + ? options + : { credentials: await this.auth.getNoneCredentials() }; + const conditionFetcher = new DataLoader( (requests: readonly QueryPermissionRequest[]) => - this.permissions.authorizeConditional(requests.slice(), options), + this.permissions.authorizeConditional(requests.slice(), compatOptions), { cacheKeyFn: ({ permission: { name } }) => name, }, @@ -102,7 +108,7 @@ export class AuthorizedSearchEngine implements SearchEngine { const authorizer = new DataLoader( (requests: readonly AuthorizePermissionRequest[]) => - this.permissions.authorize(requests.slice(), options), + this.permissions.authorize(requests.slice(), compatOptions), { // Serialize the permission name and resourceRef as // a query string to avoid collisions from overlapping @@ -159,7 +165,7 @@ export class AuthorizedSearchEngine implements SearchEngine { if (!resultByResultFilteringRequired) { return this.searchEngine.query( { ...query, types: authorizedTypes }, - options, + compatOptions, ); } @@ -174,7 +180,7 @@ export class AuthorizedSearchEngine implements SearchEngine { do { const nextPage = await this.searchEngine.query( { ...query, types: authorizedTypes, pageCursor: nextPageCursor }, - options, + compatOptions, ); filteredResults = filteredResults.concat( diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 250be2853c..d226805baf 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -145,6 +145,7 @@ export async function createRouter( inputEngine, types, permissionEvaluator, + auth, config, ) : inputEngine;