catalog-backend: migrate to support new auth services

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-16 13:24:42 +01:00
parent 58b5e450dd
commit 15ba00ff7d
17 changed files with 396 additions and 269 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---
Migrated to support new auth services. The `CatalogBuilder.create` method now accepts a `discovery` option, which is recommended to forward from the plugin environment, as it will otherwise fall back to use the `HostDiscovery` implementation.
+8 -2
View File
@@ -11,6 +11,7 @@ import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from
import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeOptions as AnalyzeOptions_2 } from '@backstage/plugin-catalog-node';
import { AuthService } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogCollatorEntityTransformer as CatalogCollatorEntityTransformer_2 } from '@backstage/plugin-search-backend-module-catalog';
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
@@ -28,6 +29,7 @@ import { Config } from '@backstage/config';
import { DefaultCatalogCollatorFactory as DefaultCatalogCollatorFactory_2 } from '@backstage/plugin-search-backend-module-catalog';
import { DefaultCatalogCollatorFactoryOptions as DefaultCatalogCollatorFactoryOptions_2 } from '@backstage/plugin-search-backend-module-catalog';
import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { EntitiesSearchFilter as EntitiesSearchFilter_2 } from '@backstage/plugin-catalog-node';
import { Entity } from '@backstage/catalog-model';
import { EntityFilter as EntityFilter_2 } from '@backstage/plugin-catalog-node';
@@ -38,15 +40,16 @@ import { EntityProviderMutation as EntityProviderMutation_2 } from '@backstage/p
import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-catalog-node';
import { EventBroker } from '@backstage/plugin-events-node';
import { GetEntitiesRequest } from '@backstage/catalog-client';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common';
import { locationSpecToLocationEntity as locationSpecToLocationEntity_2 } from '@backstage/plugin-catalog-node';
import { locationSpecToMetadataName as locationSpecToMetadataName_2 } from '@backstage/plugin-catalog-node';
import { Logger } from 'winston';
import { Permission } from '@backstage/plugin-permission-common';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { PermissionsService } from '@backstage/backend-plugin-api';
import { PlaceholderResolver as PlaceholderResolver_2 } from '@backstage/plugin-catalog-node';
import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backstage/plugin-catalog-node';
import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node';
@@ -189,8 +192,11 @@ export type CatalogEnvironment = {
database: PluginDatabaseManager;
config: Config;
reader: UrlReader;
permissions: PermissionEvaluator | PermissionAuthorizer;
permissions: PermissionsService | PermissionAuthorizer;
scheduler?: PluginTaskScheduler;
discovery?: DiscoveryService;
auth?: AuthService;
httpAuth?: HttpAuthService;
};
// @public
+10 -9
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { EntityFilter } from '@backstage/plugin-catalog-node';
@@ -48,7 +49,7 @@ export type EntitiesRequest = {
fields?: (entity: Entity) => Entity;
order?: EntityOrder[];
pagination?: EntityPagination;
authorizationToken?: string;
credentials: BackstageCredentials;
};
export type EntitiesResponse = {
@@ -75,9 +76,9 @@ export interface EntitiesBatchRequest {
*/
fields?: (entity: Entity) => Entity;
/**
* The optional token that authorizes the action.
* The credentials that authorizes the action.
*/
authorizationToken?: string;
credentials: BackstageCredentials;
}
export interface EntitiesBatchResponse {
@@ -115,9 +116,9 @@ export interface EntityFacetsRequest {
*/
facets: string[];
/**
* The optional token that authorizes the action.
* The credentials that authorizes the action.
*/
authorizationToken?: string;
credentials: BackstageCredentials;
}
/**
@@ -157,7 +158,7 @@ export interface EntitiesCatalog {
*/
removeEntityByUid(
uid: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<void>;
/**
@@ -167,7 +168,7 @@ export interface EntitiesCatalog {
*/
entityAncestry(
entityRef: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<EntityAncestryResponse>;
/**
@@ -192,7 +193,7 @@ export type QueryEntitiesRequest =
* for the current and the next pagination requests.
*/
export interface QueryEntitiesInitialRequest {
authorizationToken?: string;
credentials: BackstageCredentials;
fields?: (entity: Entity) => Entity;
limit?: number;
filter?: EntityFilter;
@@ -208,7 +209,7 @@ export interface QueryEntitiesInitialRequest {
* move forward or backward on the data.
*/
export interface QueryEntitiesCursorRequest {
authorizationToken?: string;
credentials: BackstageCredentials;
fields?: (entity: Entity) => Entity;
limit?: number;
cursor: Cursor;
@@ -23,6 +23,7 @@ import { AuthorizedEntitiesCatalog } from './AuthorizedEntitiesCatalog';
import { Cursor, QueryEntitiesResponse } from '../catalog/types';
import { Entity } from '@backstage/catalog-model';
import { EntityFilter } from '@backstage/plugin-catalog-node';
import { mockCredentials } from '@backstage/backend-test-utils';
describe('AuthorizedEntitiesCatalog', () => {
const fakeCatalog = {
@@ -60,7 +61,7 @@ describe('AuthorizedEntitiesCatalog', () => {
expect(
await catalog.entities({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
}),
).toEqual({
entities: [],
@@ -77,10 +78,10 @@ describe('AuthorizedEntitiesCatalog', () => {
]);
const catalog = createCatalog(isEntityKind);
await catalog.entities({ authorizationToken: 'abcd' });
await catalog.entities({ credentials: mockCredentials.none() });
expect(fakeCatalog.entities).toHaveBeenCalledWith({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
});
});
@@ -91,10 +92,10 @@ describe('AuthorizedEntitiesCatalog', () => {
]);
const catalog = createCatalog();
await catalog.entities({ authorizationToken: 'abcd' });
await catalog.entities({ credentials: mockCredentials.none() });
expect(fakeCatalog.entities).toHaveBeenCalledWith({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
});
});
});
@@ -109,7 +110,7 @@ describe('AuthorizedEntitiesCatalog', () => {
await expect(
catalog.entitiesBatch({
entityRefs: ['component:default/component-a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
}),
).resolves.toEqual({
items: [null],
@@ -132,12 +133,12 @@ describe('AuthorizedEntitiesCatalog', () => {
await catalog.entitiesBatch({
entityRefs: ['component:default/component-a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
});
expect(fakeCatalog.entitiesBatch).toHaveBeenCalledWith({
entityRefs: ['component:default/component-a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
});
});
@@ -150,12 +151,12 @@ describe('AuthorizedEntitiesCatalog', () => {
await catalog.entitiesBatch({
entityRefs: ['component:default/component-a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
});
expect(fakeCatalog.entitiesBatch).toHaveBeenCalledWith({
entityRefs: ['component:default/component-a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
});
});
});
@@ -169,7 +170,7 @@ describe('AuthorizedEntitiesCatalog', () => {
await expect(
catalog.queryEntities({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
}),
).resolves.toEqual({
@@ -188,12 +189,12 @@ describe('AuthorizedEntitiesCatalog', () => {
const catalog = createCatalog();
await catalog.queryEntities({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
});
expect(fakeCatalog.queryEntities).toHaveBeenCalledWith({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
});
});
@@ -243,12 +244,12 @@ describe('AuthorizedEntitiesCatalog', () => {
const catalog = createCatalog(isEntityKind);
let response = await catalog.queryEntities({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'name', values: ['name'] },
});
expect(fakeCatalog.queryEntities).toHaveBeenCalledWith({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { allOf: [{ key: 'kind', values: ['b'] }, requestFilter] },
});
@@ -276,12 +277,12 @@ describe('AuthorizedEntitiesCatalog', () => {
orderFieldValues: ['a', null],
};
response = await catalog.queryEntities({
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
cursor,
});
expect(fakeCatalog.queryEntities).toHaveBeenNthCalledWith(2, {
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
cursor: {
...cursor,
filter: { allOf: [{ key: 'kind', values: ['b'] }, requestFilter] },
@@ -324,7 +325,9 @@ describe('AuthorizedEntitiesCatalog', () => {
);
await expect(() =>
catalog.removeEntityByUid('uid', { authorizationToken: 'abcd' }),
catalog.removeEntityByUid('uid', {
credentials: mockCredentials.none(),
}),
).rejects.toThrow(NotAllowedError);
});
@@ -343,7 +346,9 @@ describe('AuthorizedEntitiesCatalog', () => {
);
await expect(() =>
catalog.removeEntityByUid('uid', { authorizationToken: 'abcd' }),
catalog.removeEntityByUid('uid', {
credentials: mockCredentials.none(),
}),
).rejects.toThrow(NotAllowedError);
});
@@ -363,9 +368,13 @@ describe('AuthorizedEntitiesCatalog', () => {
createConditionTransformer([isEntityKind]),
);
await catalog.removeEntityByUid('uid', { authorizationToken: 'abcd' });
await catalog.removeEntityByUid('uid', {
credentials: mockCredentials.none(),
});
expect(fakeCatalog.removeEntityByUid).toHaveBeenCalledWith('uid');
expect(fakeCatalog.removeEntityByUid).toHaveBeenCalledWith('uid', {
credentials: mockCredentials.none(),
});
});
it('calls underlying catalog method on ALLOW', async () => {
@@ -383,9 +392,13 @@ describe('AuthorizedEntitiesCatalog', () => {
createConditionTransformer([]),
);
await catalog.removeEntityByUid('uid', { authorizationToken: 'abcd' });
await catalog.removeEntityByUid('uid', {
credentials: mockCredentials.none(),
});
expect(fakeCatalog.removeEntityByUid).toHaveBeenCalledWith('uid');
expect(fakeCatalog.removeEntityByUid).toHaveBeenCalledWith('uid', {
credentials: mockCredentials.none(),
});
});
});
@@ -398,7 +411,7 @@ describe('AuthorizedEntitiesCatalog', () => {
await expect(() =>
catalog.entityAncestry('backstage:default/component', {
authorizationToken: 'Bearer abcd',
credentials: mockCredentials.none(),
}),
).rejects.toThrow(NotAllowedError);
});
@@ -443,7 +456,7 @@ describe('AuthorizedEntitiesCatalog', () => {
const ancestryResult = await catalog.entityAncestry(
'backstage:default/a',
{ authorizationToken: 'Bearer abcd' },
{ credentials: mockCredentials.none() },
);
expect(ancestryResult).toEqual({
@@ -476,7 +489,7 @@ describe('AuthorizedEntitiesCatalog', () => {
expect(
await catalog.facets({
facets: ['a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
}),
).toEqual({
facets: { a: [] },
@@ -492,11 +505,14 @@ describe('AuthorizedEntitiesCatalog', () => {
]);
const catalog = createCatalog(isEntityKind);
await catalog.facets({ facets: ['a'], authorizationToken: 'abcd' });
await catalog.facets({
facets: ['a'],
credentials: mockCredentials.none(),
});
expect(fakeCatalog.facets).toHaveBeenCalledWith({
facets: ['a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
filter: { key: 'kind', values: ['b'] },
});
});
@@ -507,11 +523,14 @@ describe('AuthorizedEntitiesCatalog', () => {
]);
const catalog = createCatalog();
await catalog.facets({ facets: ['a'], authorizationToken: 'abcd' });
await catalog.facets({
facets: ['a'],
credentials: mockCredentials.none(),
});
expect(fakeCatalog.facets).toHaveBeenCalledWith({
facets: ['a'],
authorizationToken: 'abcd',
credentials: mockCredentials.none(),
});
});
});
@@ -20,10 +20,7 @@ import {
catalogEntityReadPermission,
} from '@backstage/plugin-catalog-common/alpha';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import {
AuthorizeResult,
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { ConditionTransformer } from '@backstage/plugin-permission-node';
import {
Cursor,
@@ -41,19 +38,23 @@ import {
import { basicEntityFilter } from './request';
import { isQueryEntitiesCursorRequest } from './util';
import { EntityFilter } from '@backstage/plugin-catalog-node';
import {
BackstageCredentials,
PermissionsService,
} from '@backstage/backend-plugin-api';
export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
constructor(
private readonly entitiesCatalog: EntitiesCatalog,
private readonly permissionApi: PermissionEvaluator,
private readonly permissionApi: PermissionsService,
private readonly transformConditions: ConditionTransformer<EntityFilter>,
) {}
async entities(request?: EntitiesRequest): Promise<EntitiesResponse> {
async entities(request: EntitiesRequest): Promise<EntitiesResponse> {
const authorizeDecision = (
await this.permissionApi.authorizeConditional(
[{ permission: catalogEntityReadPermission }],
{ token: request?.authorizationToken },
{ credentials: request.credentials },
)
)[0];
@@ -85,7 +86,7 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
const authorizeDecision = (
await this.permissionApi.authorizeConditional(
[{ permission: catalogEntityReadPermission }],
{ token: request?.authorizationToken },
{ credentials: request.credentials },
)
)[0];
@@ -116,7 +117,7 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
const authorizeDecision = (
await this.permissionApi.authorizeConditional(
[{ permission: catalogEntityReadPermission }],
{ token: request.authorizationToken },
{ credentials: request.credentials },
)
)[0];
@@ -186,12 +187,12 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
async removeEntityByUid(
uid: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<void> {
const authorizeResponse = (
await this.permissionApi.authorizeConditional(
[{ permission: catalogEntityDeletePermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
if (authorizeResponse.result === AuthorizeResult.DENY) {
@@ -202,6 +203,7 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
authorizeResponse.conditions,
);
const { entities } = await this.entitiesCatalog.entities({
credentials: options.credentials,
filter: {
allOf: [permissionFilter, basicEntityFilter({ 'metadata.uid': uid })],
},
@@ -210,30 +212,35 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
throw new NotAllowedError();
}
}
return this.entitiesCatalog.removeEntityByUid(uid);
return this.entitiesCatalog.removeEntityByUid(uid, {
credentials: options.credentials,
});
}
async entityAncestry(
entityRef: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<EntityAncestryResponse> {
const rootEntityAuthorizeResponse = (
await this.permissionApi.authorize(
[{ permission: catalogEntityReadPermission, resourceRef: entityRef }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
if (rootEntityAuthorizeResponse.result === AuthorizeResult.DENY) {
throw new NotAllowedError();
}
const ancestryResult = await this.entitiesCatalog.entityAncestry(entityRef);
const ancestryResult = await this.entitiesCatalog.entityAncestry(
entityRef,
{ credentials: options.credentials },
);
const authorizeResponse = await this.permissionApi.authorize(
ancestryResult.items.map(item => ({
permission: catalogEntityReadPermission,
resourceRef: stringifyEntityRef(item.entity),
})),
{ token: options?.authorizationToken },
{ credentials: options.credentials },
);
const unauthorizedAncestryItems = ancestryResult.items.filter(
(_, index) => authorizeResponse[index].result === AuthorizeResult.DENY,
@@ -268,7 +275,7 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog {
const authorizeDecision = (
await this.permissionApi.authorizeConditional(
[{ permission: catalogEntityReadPermission }],
{ token: request?.authorizationToken },
{ credentials: request.credentials },
)
)[0];
@@ -17,6 +17,7 @@
import { NotAllowedError, NotFoundError } from '@backstage/errors';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { AuthorizedLocationService } from './AuthorizedLocationService';
import { mockCredentials } from '@backstage/backend-test-utils';
describe('AuthorizedLocationService', () => {
const fakeLocationService = {
@@ -45,6 +46,10 @@ describe('AuthorizedLocationService', () => {
const createService = () =>
new AuthorizedLocationService(fakeLocationService, fakePermissionApi);
const mockOptions = {
credentials: mockCredentials.none(),
};
afterEach(() => {
jest.resetAllMocks();
});
@@ -55,13 +60,12 @@ describe('AuthorizedLocationService', () => {
const service = createService();
const spec = { type: 'type', target: 'target' };
await service.createLocation(spec, false, {
authorizationToken: 'Bearer authtoken',
});
await service.createLocation(spec, false, mockOptions);
expect(fakeLocationService.createLocation).toHaveBeenCalledWith(
spec,
false,
mockOptions,
);
});
@@ -71,9 +75,7 @@ describe('AuthorizedLocationService', () => {
const spec = { type: 'type', target: 'target' };
await expect(() =>
service.createLocation(spec, false, {
authorizationToken: 'Bearer authtoken',
}),
service.createLocation(spec, false, mockOptions),
).rejects.toThrow(NotAllowedError);
});
});
@@ -83,7 +85,7 @@ describe('AuthorizedLocationService', () => {
mockAllow();
const service = createService();
await service.listLocations({ authorizationToken: 'Bearer authtoken' });
await service.listLocations(mockOptions);
expect(fakeLocationService.listLocations).toHaveBeenCalled();
});
@@ -92,9 +94,7 @@ describe('AuthorizedLocationService', () => {
mockDeny();
const service = createService();
const locations = await service.listLocations({
authorizationToken: 'Bearer authtoken',
});
const locations = await service.listLocations(mockOptions);
expect(locations).toEqual([]);
});
@@ -105,11 +105,12 @@ describe('AuthorizedLocationService', () => {
mockAllow();
const service = createService();
await service.getLocation('id', {
authorizationToken: 'Bearer authtoken',
});
await service.getLocation('id', mockOptions);
expect(fakeLocationService.getLocation).toHaveBeenCalledWith('id');
expect(fakeLocationService.getLocation).toHaveBeenCalledWith(
'id',
mockOptions,
);
});
it('throws error on DENY', async () => {
@@ -117,7 +118,7 @@ describe('AuthorizedLocationService', () => {
const service = createService();
await expect(() =>
service.getLocation('id', { authorizationToken: 'Bearer authtoken' }),
service.getLocation('id', mockOptions),
).rejects.toThrow(NotFoundError);
});
});
@@ -127,11 +128,12 @@ describe('AuthorizedLocationService', () => {
mockAllow();
const service = createService();
await service.deleteLocation('id', {
authorizationToken: 'Bearer authtoken',
});
await service.deleteLocation('id', mockOptions);
expect(fakeLocationService.deleteLocation).toHaveBeenCalledWith('id');
expect(fakeLocationService.deleteLocation).toHaveBeenCalledWith(
'id',
mockOptions,
);
});
it('throws error on DENY', async () => {
@@ -139,9 +141,7 @@ describe('AuthorizedLocationService', () => {
const service = createService();
await expect(() =>
service.deleteLocation('id', {
authorizationToken: 'Bearer authtoken',
}),
service.deleteLocation('id', mockOptions),
).rejects.toThrow(NotAllowedError);
});
});
@@ -153,16 +153,17 @@ describe('AuthorizedLocationService', () => {
await service.getLocationByEntity(
{ kind: 'c', namespace: 'ns', name: 'n' },
{
authorizationToken: 'Bearer authtoken',
},
mockOptions,
);
expect(fakeLocationService.getLocationByEntity).toHaveBeenCalledWith({
kind: 'c',
namespace: 'ns',
name: 'n',
});
expect(fakeLocationService.getLocationByEntity).toHaveBeenCalledWith(
{
kind: 'c',
namespace: 'ns',
name: 'n',
},
mockOptions,
);
});
it('throws error on DENY', async () => {
@@ -172,7 +173,7 @@ describe('AuthorizedLocationService', () => {
await expect(() =>
service.getLocationByEntity(
{ kind: 'c', namespace: 'ns', name: 'n' },
{ authorizationToken: 'Bearer authtoken' },
mockOptions,
),
).rejects.toThrow(NotFoundError);
});
@@ -22,23 +22,24 @@ import {
catalogLocationDeletePermission,
catalogLocationReadPermission,
} from '@backstage/plugin-catalog-common/alpha';
import {
AuthorizeResult,
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { LocationInput, LocationService } from './types';
import {
BackstageCredentials,
PermissionsService,
} from '@backstage/backend-plugin-api';
export class AuthorizedLocationService implements LocationService {
constructor(
private readonly locationService: LocationService,
private readonly permissionApi: PermissionEvaluator,
private readonly permissionApi: PermissionsService,
) {}
async createLocation(
spec: LocationInput,
dryRun: boolean,
options?: {
authorizationToken?: string;
options: {
credentials: BackstageCredentials;
},
): Promise<{
location: Location;
@@ -48,7 +49,7 @@ export class AuthorizedLocationService implements LocationService {
const authorizationResponse = (
await this.permissionApi.authorize(
[{ permission: catalogLocationCreatePermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
@@ -56,16 +57,16 @@ export class AuthorizedLocationService implements LocationService {
throw new NotAllowedError();
}
return this.locationService.createLocation(spec, dryRun);
return this.locationService.createLocation(spec, dryRun, options);
}
async listLocations(options?: {
authorizationToken?: string;
async listLocations(options: {
credentials: BackstageCredentials;
}): Promise<Location[]> {
const authorizationResponse = (
await this.permissionApi.authorize(
[{ permission: catalogLocationReadPermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
@@ -73,17 +74,17 @@ export class AuthorizedLocationService implements LocationService {
return [];
}
return this.locationService.listLocations();
return this.locationService.listLocations(options);
}
async getLocation(
id: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<Location> {
const authorizationResponse = (
await this.permissionApi.authorize(
[{ permission: catalogLocationReadPermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
@@ -91,17 +92,17 @@ export class AuthorizedLocationService implements LocationService {
throw new NotFoundError(`Found no location with ID ${id}`);
}
return this.locationService.getLocation(id);
return this.locationService.getLocation(id, options);
}
async deleteLocation(
id: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<void> {
const authorizationResponse = (
await this.permissionApi.authorize(
[{ permission: catalogLocationDeletePermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
@@ -109,23 +110,23 @@ export class AuthorizedLocationService implements LocationService {
throw new NotAllowedError();
}
return this.locationService.deleteLocation(id);
return this.locationService.deleteLocation(id, options);
}
async getLocationByEntity(
entityRef: CompoundEntityRef | string,
options?: { authorizationToken?: string | undefined } | undefined,
options: { credentials: BackstageCredentials },
): Promise<Location> {
const authorizationResponse = (
await this.permissionApi.authorize(
[{ permission: catalogLocationReadPermission }],
{ token: options?.authorizationToken },
{ credentials: options.credentials },
)
)[0];
if (authorizationResponse.result === AuthorizeResult.DENY) {
throw new NotFoundError();
}
return this.locationService.getLocationByEntity(entityRef);
return this.locationService.getLocationByEntity(entityRef, options);
}
}
@@ -18,6 +18,7 @@ import { NotAllowedError } from '@backstage/errors';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { AuthorizedRefreshService } from './AuthorizedRefreshService';
import { mockCredentials } from '@backstage/backend-test-utils';
describe('AuthorizedRefreshService', () => {
const refreshService = {
@@ -46,7 +47,7 @@ describe('AuthorizedRefreshService', () => {
await expect(() =>
authorizedService.refresh({
entityRef: 'some entity ref',
authorizationToken: 'some auth token',
credentials: mockCredentials.none(),
}),
).rejects.toThrow(NotAllowedError);
});
@@ -64,7 +65,7 @@ describe('AuthorizedRefreshService', () => {
const options = {
entityRef: 'some entity ref',
authorizationToken: 'some auth token',
credentials: mockCredentials.none(),
};
await authorizedService.refresh(options);
@@ -16,16 +16,14 @@
import { NotAllowedError } from '@backstage/errors';
import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha';
import {
AuthorizeResult,
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { RefreshOptions, RefreshService } from './types';
import { PermissionsService } from '@backstage/backend-plugin-api';
export class AuthorizedRefreshService implements RefreshService {
constructor(
private readonly service: RefreshService,
private readonly permissionApi: PermissionEvaluator,
private readonly permissionApi: PermissionsService,
) {}
async refresh(options: RefreshOptions) {
@@ -37,7 +35,7 @@ export class AuthorizedRefreshService implements RefreshService {
resourceRef: options.entityRef,
},
],
{ token: options.authorizationToken },
{ credentials: options.credentials },
)
)[0];
if (authorizeDecision.result !== AuthorizeResult.ALLOW) {
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common';
import {
PluginDatabaseManager,
HostDiscovery,
UrlReader,
createLegacyAuthAdapters,
} from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import {
DefaultNamespaceEntityPolicy,
@@ -84,7 +89,6 @@ import { permissionRules as catalogPermissionRules } from '../permissions/rules'
import { PermissionRule } from '@backstage/plugin-permission-node';
import {
PermissionAuthorizer,
PermissionEvaluator,
toPermissionEvaluator,
} from '@backstage/plugin-permission-common';
import {
@@ -102,6 +106,12 @@ import { DefaultProviderDatabase } from '../database/DefaultProviderDatabase';
import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase';
import { EventBroker } from '@backstage/plugin-events-node';
import { durationToMilliseconds } from '@backstage/types';
import {
DiscoveryService,
AuthService,
HttpAuthService,
PermissionsService,
} from '@backstage/backend-plugin-api';
/**
* This is a duplicate of the alpha `CatalogPermissionRule` type, for use in the stable API.
@@ -118,8 +128,11 @@ export type CatalogEnvironment = {
database: PluginDatabaseManager;
config: Config;
reader: UrlReader;
permissions: PermissionEvaluator | PermissionAuthorizer;
permissions: PermissionsService | PermissionAuthorizer;
scheduler?: PluginTaskScheduler;
discovery?: DiscoveryService;
auth?: AuthService;
httpAuth?: HttpAuthService;
};
/**
@@ -438,7 +451,19 @@ export class CatalogBuilder {
processingEngine: CatalogProcessingEngine;
router: Router;
}> {
const { config, database, logger, permissions, scheduler } = this.env;
const {
config,
database,
logger,
permissions,
scheduler,
discovery = HostDiscovery.fromConfig(config),
} = this.env;
const { auth, httpAuth } = createLegacyAuthAdapters({
...this.env,
discovery,
});
const policy = this.buildEntityPolicy();
const processors = this.buildProcessors();
@@ -486,25 +511,26 @@ export class CatalogBuilder {
stitcher,
});
let permissionEvaluator: PermissionEvaluator;
let permissionsService: PermissionsService;
if ('authorizeConditional' in permissions) {
permissionEvaluator = permissions as PermissionEvaluator;
permissionsService = permissions as PermissionsService;
} else {
logger.warn(
'PermissionAuthorizer is deprecated. Please use an instance of PermissionEvaluator instead of PermissionAuthorizer in PluginEnvironment#permissions',
);
permissionEvaluator = toPermissionEvaluator(permissions);
permissionsService = toPermissionEvaluator(permissions);
}
const entitiesCatalog = new AuthorizedEntitiesCatalog(
unauthorizedEntitiesCatalog,
permissionEvaluator,
permissionsService,
createConditionTransformer(this.permissionRules),
);
const permissionIntegrationRouter = createPermissionIntegrationRouter({
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
getResources: async (resourceRefs: string[]) => {
const { entities } = await unauthorizedEntitiesCatalog.entities({
credentials: await auth.getOwnServiceCredentials(),
filter: {
anyOf: resourceRefs.map(resourceRef => {
const { kind, namespace, name } = parseEntityRef(resourceRef);
@@ -558,12 +584,13 @@ export class CatalogBuilder {
new DefaultLocationService(locationStore, orchestrator, {
allowedLocationTypes: this.allowedLocationType,
}),
permissionEvaluator,
permissionsService,
);
const refreshService = new AuthorizedRefreshService(
new DefaultRefreshService({ database: catalogDatabase }),
permissionEvaluator,
permissionsService,
);
const router = await createRouter({
entitiesCatalog,
locationAnalyzer,
@@ -573,6 +600,8 @@ export class CatalogBuilder {
logger,
config,
permissionIntegrationRouter,
auth,
httpAuth,
});
await connectEntityProviders(providerDatabase, entityProviders);
@@ -178,6 +178,9 @@ export const catalogPlugin = createBackendPlugin({
httpRouter: coreServices.httpRouter,
lifecycle: coreServices.lifecycle,
scheduler: coreServices.scheduler,
discovery: coreServices.discovery,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
},
async init({
logger,
@@ -188,6 +191,9 @@ export const catalogPlugin = createBackendPlugin({
httpRouter,
lifecycle,
scheduler,
discovery,
auth,
httpAuth,
}) {
const winstonLogger = loggerToWinstonLogger(logger);
const builder = await CatalogBuilder.create({
@@ -197,6 +203,9 @@ export const catalogPlugin = createBackendPlugin({
database,
scheduler,
logger: winstonLogger,
discovery,
auth,
httpAuth,
});
if (processingExtensions.onProcessingErrorHandler) {
builder.subscribe({
@@ -15,7 +15,11 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import {
TestDatabaseId,
TestDatabases,
mockCredentials,
} from '@backstage/backend-test-utils';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { Knex } from 'knex';
import { v4 as uuid, v4 } from 'uuid';
@@ -304,8 +308,10 @@ describe('DefaultEntitiesCatalog', () => {
const testFilter = {
key: 'spec.test',
};
const request = { filter: testFilter };
const { entities } = await catalog.entities(request);
const { entities } = await catalog.entities({
filter: testFilter,
credentials: mockCredentials.none(),
});
expect(entities.length).toBe(1);
expect(entities[0]).toEqual(entity2);
@@ -343,8 +349,10 @@ describe('DefaultEntitiesCatalog', () => {
key: 'spec.test',
},
};
const request = { filter: testFilter };
const { entities } = await catalog.entities(request);
const { entities } = await catalog.entities({
filter: testFilter,
credentials: mockCredentials.none(),
});
expect(entities.length).toBe(1);
expect(entities[0]).toEqual(entity1);
@@ -406,7 +414,7 @@ describe('DefaultEntitiesCatalog', () => {
values: ['red'],
},
};
const request = {
const { entities } = await catalog.entities({
filter: {
allOf: [
testFilter1,
@@ -415,8 +423,8 @@ describe('DefaultEntitiesCatalog', () => {
},
],
},
};
const { entities } = await catalog.entities(request);
credentials: mockCredentials.none(),
});
expect(entities.length).toBe(2);
expect(entities).toContainEqual(entity2);
@@ -455,14 +463,15 @@ describe('DefaultEntitiesCatalog', () => {
const testFilter2 = {
key: 'metadata.desc',
};
const request = {
const { entities } = await catalog.entities({
filter: {
not: {
allOf: [testFilter1, testFilter2],
},
},
};
const { entities } = await catalog.entities(request);
credentials: mockCredentials.none(),
});
expect(entities.length).toBe(1);
expect(entities).toContainEqual(entity1);
@@ -498,8 +507,10 @@ describe('DefaultEntitiesCatalog', () => {
key: 'kind',
values: [],
};
const request = { filter: testFilter };
const { entities } = await catalog.entities(request);
const { entities } = await catalog.entities({
filter: testFilter,
credentials: mockCredentials.none(),
});
expect(entities.length).toBe(0);
},
@@ -603,9 +614,11 @@ describe('DefaultEntitiesCatalog', () => {
stitcher,
});
function f(request: EntitiesRequest): Promise<string[]> {
function f(
request: Omit<EntitiesRequest, 'credentials'>,
): Promise<string[]> {
return catalog
.entities(request)
.entities({ ...request, credentials: mockCredentials.none() })
.then(response => response.entities.map(e => e.metadata.name));
}
@@ -701,6 +714,7 @@ describe('DefaultEntitiesCatalog', () => {
'k:default/does-not-exist',
'k:default/two',
],
credentials: mockCredentials.none(),
});
expect(items.map(e => e && stringifyEntityRef(e))).toEqual([
@@ -749,6 +763,7 @@ describe('DefaultEntitiesCatalog', () => {
const { items } = await catalog.entitiesBatch({
entityRefs: ['k:default/two', 'k:default/one'],
filter: { key: 'spec.owner', values: ['me'] },
credentials: mockCredentials.none(),
});
expect(items.map(e => e && stringifyEntityRef(e))).toEqual([
@@ -804,6 +819,7 @@ describe('DefaultEntitiesCatalog', () => {
filter,
limit,
orderFields: [{ field: 'metadata.name', order: 'asc' }],
credentials: mockCredentials.none(),
};
const response1 = await catalog.queryEntities(request1);
expect(response1.items).toEqual([entityFrom('A'), entityFrom('B')]);
@@ -815,6 +831,7 @@ describe('DefaultEntitiesCatalog', () => {
const request2: QueryEntitiesCursorRequest = {
cursor: response1.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response2 = await catalog.queryEntities(request2);
expect(response2.items).toEqual([entityFrom('C'), entityFrom('D')]);
@@ -826,6 +843,7 @@ describe('DefaultEntitiesCatalog', () => {
const request3: QueryEntitiesCursorRequest = {
cursor: response2.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response3 = await catalog.queryEntities(request3);
expect(response3.items).toEqual([entityFrom('E'), entityFrom('F')]);
@@ -837,6 +855,7 @@ describe('DefaultEntitiesCatalog', () => {
const request4: QueryEntitiesCursorRequest = {
cursor: response3.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response4 = await catalog.queryEntities(request4);
expect(response4.items).toEqual([entityFrom('C'), entityFrom('D')]);
@@ -848,6 +867,7 @@ describe('DefaultEntitiesCatalog', () => {
const request5: QueryEntitiesCursorRequest = {
cursor: response4.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response5 = await catalog.queryEntities(request5);
expect(response5.items).toEqual([entityFrom('A'), entityFrom('B')]);
@@ -859,6 +879,7 @@ describe('DefaultEntitiesCatalog', () => {
const request6: QueryEntitiesCursorRequest = {
cursor: response5.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response6 = await catalog.queryEntities(request6);
expect(response6.items).toEqual([entityFrom('C'), entityFrom('D')]);
@@ -870,6 +891,7 @@ describe('DefaultEntitiesCatalog', () => {
const request7: QueryEntitiesCursorRequest = {
cursor: response6.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response7 = await catalog.queryEntities(request7);
expect(response7.items).toEqual([entityFrom('E'), entityFrom('F')]);
@@ -881,6 +903,7 @@ describe('DefaultEntitiesCatalog', () => {
const request7bis: QueryEntitiesCursorRequest = {
cursor: response6.pageInfo.nextCursor!,
limit: limit + 1,
credentials: mockCredentials.none(),
};
const response7bis = await catalog.queryEntities(request7bis);
expect(response7bis.items).toEqual([
@@ -896,6 +919,7 @@ describe('DefaultEntitiesCatalog', () => {
const request8: QueryEntitiesCursorRequest = {
cursor: response7.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response8 = await catalog.queryEntities(request8);
expect(response8.items).toEqual([entityFrom('G')]);
@@ -949,6 +973,7 @@ describe('DefaultEntitiesCatalog', () => {
filter,
limit,
orderFields: [{ field: 'metadata.name', order: 'desc' }],
credentials: mockCredentials.none(),
};
const response1 = await catalog.queryEntities(request1);
expect(response1.items).toEqual([entityFrom('G'), entityFrom('F')]);
@@ -960,6 +985,7 @@ describe('DefaultEntitiesCatalog', () => {
const request2: QueryEntitiesCursorRequest = {
cursor: response1.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response2 = await catalog.queryEntities(request2);
expect(response2.items).toEqual([entityFrom('E'), entityFrom('D')]);
@@ -971,6 +997,7 @@ describe('DefaultEntitiesCatalog', () => {
const request3: QueryEntitiesCursorRequest = {
cursor: response2.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response3 = await catalog.queryEntities(request3);
expect(response3.items).toEqual([entityFrom('C'), entityFrom('B')]);
@@ -982,6 +1009,7 @@ describe('DefaultEntitiesCatalog', () => {
const request4: QueryEntitiesCursorRequest = {
cursor: response3.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response4 = await catalog.queryEntities(request4);
@@ -994,6 +1022,7 @@ describe('DefaultEntitiesCatalog', () => {
const request5: QueryEntitiesCursorRequest = {
cursor: response4.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response5 = await catalog.queryEntities(request5);
expect(response5.items).toEqual([entityFrom('G'), entityFrom('F')]);
@@ -1005,6 +1034,7 @@ describe('DefaultEntitiesCatalog', () => {
const request6: QueryEntitiesCursorRequest = {
cursor: response5.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response6 = await catalog.queryEntities(request6);
expect(response6.items).toEqual([entityFrom('E'), entityFrom('D')]);
@@ -1016,6 +1046,7 @@ describe('DefaultEntitiesCatalog', () => {
const request7: QueryEntitiesCursorRequest = {
cursor: response6.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response7 = await catalog.queryEntities(request7);
expect(response7.items).toEqual([entityFrom('C'), entityFrom('B')]);
@@ -1027,6 +1058,7 @@ describe('DefaultEntitiesCatalog', () => {
const request7bis: QueryEntitiesCursorRequest = {
cursor: response6.pageInfo.nextCursor!,
limit: limit + 1,
credentials: mockCredentials.none(),
};
const response7bis = await catalog.queryEntities(request7bis);
expect(response7bis.items).toEqual([
@@ -1042,6 +1074,7 @@ describe('DefaultEntitiesCatalog', () => {
const request8: QueryEntitiesCursorRequest = {
cursor: response7.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response8 = await catalog.queryEntities(request8);
expect(response8.items).toEqual([entityFrom('A')]);
@@ -1094,6 +1127,7 @@ describe('DefaultEntitiesCatalog', () => {
orderFields: [{ field: 'metadata.name', order: 'asc' }],
fullTextFilter: { term: 'cAt ' },
credentials: mockCredentials.none(),
};
const response = await catalog.queryEntities(request);
expect(response.items).toEqual([
@@ -1152,6 +1186,7 @@ describe('DefaultEntitiesCatalog', () => {
filter,
limit: 100,
fullTextFilter: { term: 'cAt ', fields: ['metadata.title'] },
credentials: mockCredentials.none(),
};
const response = await catalog.queryEntities(request);
expect(response.items).toEqual([
@@ -1177,6 +1212,7 @@ describe('DefaultEntitiesCatalog', () => {
const paginatedResponseNext = await catalog.queryEntities({
cursor: paginatedResponse.pageInfo.nextCursor!,
credentials: mockCredentials.none(),
});
expect(paginatedResponseNext.items).toEqual([
entityFrom('4', { uid: 'id4', title: 'dogcat' }),
@@ -1187,6 +1223,7 @@ describe('DefaultEntitiesCatalog', () => {
const paginatedResponsePrev = await catalog.queryEntities({
cursor: paginatedResponseNext.pageInfo.prevCursor!,
credentials: mockCredentials.none(),
});
expect(paginatedResponsePrev).toMatchObject(paginatedResponse);
},
@@ -1251,6 +1288,7 @@ describe('DefaultEntitiesCatalog', () => {
term: 'KiNg ',
fields: ['metadata.title', 'metadata.name'],
},
credentials: mockCredentials.none(),
};
const response = await catalog.queryEntities(request);
@@ -1278,6 +1316,7 @@ describe('DefaultEntitiesCatalog', () => {
const paginatedResponseNext = await catalog.queryEntities({
cursor: paginatedResponse.pageInfo.nextCursor!,
credentials: mockCredentials.none(),
});
expect(paginatedResponseNext.items).toEqual([
entityFrom('NotACatKing', { uid: 'id2', title: 'atcatss' }),
@@ -1289,6 +1328,7 @@ describe('DefaultEntitiesCatalog', () => {
const paginatedResponsePrev = await catalog.queryEntities({
cursor: paginatedResponseNext.pageInfo.prevCursor!,
credentials: mockCredentials.none(),
});
expect(paginatedResponsePrev).toMatchObject(paginatedResponse);
},
@@ -1319,6 +1359,7 @@ describe('DefaultEntitiesCatalog', () => {
const request: QueryEntitiesInitialRequest = {
limit: 0,
credentials: mockCredentials.none(),
};
const response = await catalog.queryEntities(request);
expect(response).toEqual({ totalItems: 20, items: [], pageInfo: {} });
@@ -1351,6 +1392,7 @@ describe('DefaultEntitiesCatalog', () => {
const request1: QueryEntitiesInitialRequest = {
limit,
orderFields: [{ field: 'metadata.name', order: 'asc' }],
credentials: mockCredentials.none(),
};
const response1 = await catalog.queryEntities(request1);
expect(response1.items).toMatchObject([
@@ -1365,6 +1407,7 @@ describe('DefaultEntitiesCatalog', () => {
const request2: QueryEntitiesCursorRequest = {
cursor: response1.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response2 = await catalog.queryEntities(request2);
expect(response2.items).toMatchObject([
@@ -1379,6 +1422,7 @@ describe('DefaultEntitiesCatalog', () => {
const request3: QueryEntitiesCursorRequest = {
cursor: response2.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response3 = await catalog.queryEntities(request3);
expect(response3.items).toEqual([entityFrom('CC'), entityFrom('DD')]);
@@ -1390,6 +1434,7 @@ describe('DefaultEntitiesCatalog', () => {
const request4: QueryEntitiesCursorRequest = {
cursor: response3.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response4 = await catalog.queryEntities(request4);
expect(response4.items).toMatchObject([
@@ -1404,6 +1449,7 @@ describe('DefaultEntitiesCatalog', () => {
const request5: QueryEntitiesCursorRequest = {
cursor: response4.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response5 = await catalog.queryEntities(request5);
expect(response5.items).toMatchObject([
@@ -1471,6 +1517,7 @@ describe('DefaultEntitiesCatalog', () => {
values: ['included'],
},
orderFields: [{ field: 'metadata.name', order: 'asc' }],
credentials: mockCredentials.none(),
};
const response1 = await catalog.queryEntities(request1);
expect(response1.items).toMatchObject([
@@ -1485,6 +1532,7 @@ describe('DefaultEntitiesCatalog', () => {
const request2: QueryEntitiesCursorRequest = {
cursor: response1.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response2 = await catalog.queryEntities(request2);
expect(response2.items).toMatchObject([
@@ -1528,6 +1576,7 @@ describe('DefaultEntitiesCatalog', () => {
// initial request
const request1: QueryEntitiesInitialRequest = {
limit,
credentials: mockCredentials.none(),
};
const response1 = await catalog.queryEntities(request1);
expect(response1.items).toMatchObject([
@@ -1542,6 +1591,7 @@ describe('DefaultEntitiesCatalog', () => {
const request2: QueryEntitiesCursorRequest = {
cursor: response1.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response2 = await catalog.queryEntities(request2);
expect(response2.items).toMatchObject([
@@ -1556,6 +1606,7 @@ describe('DefaultEntitiesCatalog', () => {
const request3: QueryEntitiesCursorRequest = {
cursor: response2.pageInfo.nextCursor!,
limit,
credentials: mockCredentials.none(),
};
const response3 = await catalog.queryEntities(request3);
expect(response3.items).toMatchObject([
@@ -1570,6 +1621,7 @@ describe('DefaultEntitiesCatalog', () => {
const request4: QueryEntitiesCursorRequest = {
cursor: response3.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response4 = await catalog.queryEntities(request4);
expect(response4.items).toMatchObject([
@@ -1584,6 +1636,7 @@ describe('DefaultEntitiesCatalog', () => {
const request5: QueryEntitiesCursorRequest = {
cursor: response4.pageInfo.prevCursor!,
limit,
credentials: mockCredentials.none(),
};
const response5 = await catalog.queryEntities(request5);
expect(response5.items).toMatchObject([
@@ -1719,7 +1772,12 @@ describe('DefaultEntitiesCatalog', () => {
stitcher,
});
await expect(catalog.facets({ facets: ['kind'] })).resolves.toEqual({
await expect(
catalog.facets({
facets: ['kind'],
credentials: mockCredentials.none(),
}),
).resolves.toEqual({
facets: {
kind: [
{ value: 'k', count: 2 },
@@ -1732,6 +1790,7 @@ describe('DefaultEntitiesCatalog', () => {
catalog.facets({
facets: ['kind'],
filter: { not: { key: 'metadata.name', values: ['two'] } },
credentials: mockCredentials.none(),
}),
).resolves.toEqual({
facets: {
@@ -1775,6 +1834,7 @@ describe('DefaultEntitiesCatalog', () => {
await expect(
catalog.facets({
facets: ['metadata.annotations.a.b/c.d', 'metadata.labels.e.f/g.h'],
credentials: mockCredentials.none(),
}),
).resolves.toEqual({
facets: {
@@ -1823,6 +1883,7 @@ describe('DefaultEntitiesCatalog', () => {
await expect(
catalog.facets({
facets: ['metadata.tags'],
credentials: mockCredentials.none(),
}),
).resolves.toEqual({
facets: {
@@ -15,7 +15,11 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import {
TestDatabaseId,
TestDatabases,
mockCredentials,
} from '@backstage/backend-test-utils';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { createHash } from 'crypto';
import { Knex } from 'knex';
@@ -220,6 +224,7 @@ describe('DefaultRefreshService', () => {
await refreshService.refresh({
entityRef: 'component:default/mycomp',
credentials: mockCredentials.none(),
});
await expect(
@@ -273,6 +278,7 @@ describe('DefaultRefreshService', () => {
await refreshService.refresh({
entityRef: 'api:default/myapi',
credentials: mockCredentials.none(),
});
await expect(waitForRefresh(knex, 'api:default/myapi')).resolves.toBe(
@@ -324,6 +330,7 @@ describe('DefaultRefreshService', () => {
await refreshService.refresh({
entityRef: 'component:default/mycomp',
credentials: mockCredentials.none(),
});
await expect(
@@ -334,6 +341,7 @@ describe('DefaultRefreshService', () => {
await refreshService.refresh({
entityRef: 'component:default/mycomp',
credentials: mockCredentials.none(),
});
await expect(
@@ -41,6 +41,7 @@ import { z } from 'zod';
import { decodeCursor, encodeCursor } from './util';
import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils';
import { Server } from 'http';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
describe('createRouter readonly disabled', () => {
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
@@ -75,6 +76,8 @@ describe('createRouter readonly disabled', () => {
refreshService,
config: new ConfigReader(undefined),
permissionIntegrationRouter: express.Router(),
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
});
app = wrapInOpenApiTestServer(express().use(router));
});
@@ -88,15 +91,30 @@ describe('createRouter 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',
credentials: mockCredentials.user(),
});
});
it('should support passing the token in the request body for backwards compatibility', async () => {
const response = await request(app)
.post('/refresh')
.set('Content-Type', 'application/json')
.send({
entityRef: 'Component/default:foo',
authorizationToken: mockCredentials.user.token('user:default/other'),
});
expect(response.status).toBe(200);
expect(refreshService.refresh).toHaveBeenCalledWith({
entityRef: 'Component/default:foo',
credentials: mockCredentials.user('user:default/other'),
});
});
});
describe('GET /entities', () => {
it('happy path: lists entities', async () => {
const entities: Entity[] = [
@@ -137,6 +155,7 @@ describe('createRouter readonly disabled', () => {
{ allOf: [{ key: 'c', values: ['4'] }] },
],
},
credentials: mockCredentials.user(),
});
});
});
@@ -196,6 +215,7 @@ describe('createRouter readonly disabled', () => {
fields: undefined,
term: '',
},
credentials: mockCredentials.user(),
});
});
@@ -235,6 +255,7 @@ describe('createRouter readonly disabled', () => {
fields: undefined,
term: '',
},
credentials: mockCredentials.user(),
});
});
@@ -257,6 +278,7 @@ describe('createRouter readonly disabled', () => {
expect(entitiesCatalog.queryEntities).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.queryEntities).toHaveBeenCalledWith({
cursor,
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual({
@@ -291,6 +313,7 @@ describe('createRouter readonly disabled', () => {
expect(entitiesCatalog.queryEntities).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.queryEntities).toHaveBeenCalledWith({
cursor,
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual({
@@ -370,6 +393,7 @@ describe('createRouter readonly disabled', () => {
expect(entitiesCatalog.entities).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.entities).toHaveBeenCalledWith({
filter: basicEntityFilter({ 'metadata.uid': 'zzz' }),
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual(expect.objectContaining(entity));
@@ -386,6 +410,7 @@ describe('createRouter readonly disabled', () => {
expect(entitiesCatalog.entities).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.entities).toHaveBeenCalledWith({
filter: basicEntityFilter({ 'metadata.uid': 'zzz' }),
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(404);
expect(response.text).toMatch(/uid/);
@@ -416,6 +441,7 @@ describe('createRouter readonly disabled', () => {
'metadata.namespace': 'ns',
'metadata.name': 'n',
}),
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual(expect.objectContaining(entity));
@@ -436,6 +462,7 @@ describe('createRouter readonly disabled', () => {
'metadata.namespace': 'd',
'metadata.name': 'c',
}),
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(404);
expect(response.text).toMatch(/name/);
@@ -446,13 +473,10 @@ describe('createRouter readonly disabled', () => {
it('can remove', async () => {
entitiesCatalog.removeEntityByUid.mockResolvedValue(undefined);
const response = await request(app)
.delete('/entities/by-uid/apa')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).delete('/entities/by-uid/apa');
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledWith('apa', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(204);
});
@@ -462,13 +486,10 @@ describe('createRouter readonly disabled', () => {
new NotFoundError('nope'),
);
const response = await request(app)
.delete('/entities/by-uid/apa')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).delete('/entities/by-uid/apa');
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledWith('apa', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(404);
});
@@ -518,6 +539,7 @@ describe('createRouter readonly disabled', () => {
expect(entitiesCatalog.entitiesBatch).toHaveBeenCalledWith({
entityRefs: [entityRef],
fields: expect.any(Function),
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual({ items: [entity] });
@@ -531,13 +553,10 @@ describe('createRouter readonly disabled', () => {
];
locationService.listLocations.mockResolvedValueOnce(locations);
const response = await request(app)
.get('/locations')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations');
expect(locationService.listLocations).toHaveBeenCalledTimes(1);
expect(locationService.listLocations).toHaveBeenCalledWith({
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
expect(response.body).toEqual([
@@ -555,13 +574,10 @@ describe('createRouter readonly disabled', () => {
};
locationService.getLocation.mockResolvedValueOnce(location);
const response = await request(app)
.get('/locations/foo')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations/foo');
expect(locationService.getLocation).toHaveBeenCalledTimes(1);
expect(locationService.getLocation).toHaveBeenCalledWith('foo', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
@@ -582,7 +598,7 @@ describe('createRouter readonly disabled', () => {
const response = await request(app)
.post('/locations')
.set('authorization', 'Bearer someauthtoken')
.send(spec);
expect(locationService.createLocation).not.toHaveBeenCalled();
@@ -602,12 +618,12 @@ describe('createRouter readonly disabled', () => {
const response = await request(app)
.post('/locations')
.set('authorization', 'Bearer someauthtoken')
.send(spec);
expect(locationService.createLocation).toHaveBeenCalledTimes(1);
expect(locationService.createLocation).toHaveBeenCalledWith(spec, false, {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(201);
expect(response.body).toEqual(
@@ -630,12 +646,12 @@ describe('createRouter readonly disabled', () => {
const response = await request(app)
.post('/locations?dryRun=true')
.set('authorization', 'Bearer someauthtoken')
.send(spec);
expect(locationService.createLocation).toHaveBeenCalledTimes(1);
expect(locationService.createLocation).toHaveBeenCalledWith(spec, true, {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(201);
expect(response.body).toEqual(
@@ -650,13 +666,10 @@ describe('createRouter readonly disabled', () => {
it('deletes the location', async () => {
locationService.deleteLocation.mockResolvedValueOnce(undefined);
const response = await request(app)
.delete('/locations/foo')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).delete('/locations/foo');
expect(locationService.deleteLocation).toHaveBeenCalledTimes(1);
expect(locationService.deleteLocation).toHaveBeenCalledWith('foo', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(204);
@@ -672,15 +685,12 @@ describe('createRouter readonly disabled', () => {
};
locationService.getLocationByEntity.mockResolvedValueOnce(location);
const response = await request(app)
.get('/locations/by-entity/c/ns/n')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations/by-entity/c/ns/n');
expect(locationService.getLocationByEntity).toHaveBeenCalledTimes(1);
expect(locationService.getLocationByEntity).toHaveBeenCalledWith(
{ kind: 'c', namespace: 'ns', name: 'n' },
{
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
},
);
@@ -837,6 +847,8 @@ describe('createRouter readonly enabled', () => {
},
}),
permissionIntegrationRouter: express.Router(),
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
});
app = express().use(router);
});
@@ -866,13 +878,10 @@ describe('createRouter readonly enabled', () => {
describe('DELETE /entities/by-uid/:uid', () => {
// this delete is allowed as there is no other way to remove entities
it('is allowed', async () => {
const response = await request(app)
.delete('/entities/by-uid/apa')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).delete('/entities/by-uid/apa');
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledTimes(1);
expect(entitiesCatalog.removeEntityByUid).toHaveBeenCalledWith('apa', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(204);
});
@@ -885,13 +894,10 @@ describe('createRouter readonly enabled', () => {
];
locationService.listLocations.mockResolvedValueOnce(locations);
const response = await request(app)
.get('/locations')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations');
expect(locationService.listLocations).toHaveBeenCalledTimes(1);
expect(locationService.listLocations).toHaveBeenCalledWith({
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
@@ -910,13 +916,10 @@ describe('createRouter readonly enabled', () => {
};
locationService.getLocation.mockResolvedValueOnce(location);
const response = await request(app)
.get('/locations/foo')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations/foo');
expect(locationService.getLocation).toHaveBeenCalledTimes(1);
expect(locationService.getLocation).toHaveBeenCalledWith('foo', {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(200);
@@ -937,7 +940,7 @@ describe('createRouter readonly enabled', () => {
const response = await request(app)
.post('/locations')
.set('authorization', 'Bearer someauthtoken')
.send(spec);
expect(locationService.createLocation).not.toHaveBeenCalled();
@@ -958,12 +961,12 @@ describe('createRouter readonly enabled', () => {
const response = await request(app)
.post('/locations?dryRun=true')
.set('authorization', 'Bearer someauthtoken')
.send(spec);
expect(locationService.createLocation).toHaveBeenCalledTimes(1);
expect(locationService.createLocation).toHaveBeenCalledWith(spec, true, {
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
});
expect(response.status).toEqual(201);
expect(response.body).toEqual(
@@ -976,10 +979,7 @@ describe('createRouter readonly enabled', () => {
describe('DELETE /locations', () => {
it('is not allowed', async () => {
const response = await request(app)
.delete('/locations/foo')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).delete('/locations/foo');
expect(locationService.deleteLocation).not.toHaveBeenCalled();
expect(response.status).toEqual(403);
});
@@ -994,15 +994,12 @@ describe('createRouter readonly enabled', () => {
};
locationService.getLocationByEntity.mockResolvedValueOnce(location);
const response = await request(app)
.get('/locations/by-entity/c/ns/n')
.set('authorization', 'Bearer someauthtoken');
const response = await request(app).get('/locations/by-entity/c/ns/n');
expect(locationService.getLocationByEntity).toHaveBeenCalledTimes(1);
expect(locationService.getLocationByEntity).toHaveBeenCalledWith(
{ kind: 'c', namespace: 'ns', name: 'n' },
{
authorizationToken: 'someauthtoken',
credentials: mockCredentials.user(),
},
);
@@ -1065,6 +1062,8 @@ describe('NextRouter permissioning', () => {
),
),
}),
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
});
app = express().use(router);
});
@@ -41,7 +41,7 @@ import {
} from './request';
import { parseEntityFacetParams } from './request/parseEntityFacetParams';
import { parseEntityOrderParams } from './request/parseEntityOrderParams';
import { LocationService, RefreshOptions, RefreshService } from './types';
import { LocationService, RefreshService } from './types';
import {
disallowReadonlyMode,
encodeCursor,
@@ -50,8 +50,8 @@ import {
} from './util';
import { createOpenApiRouter } from '../schema/openapi.generated';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
import { parseEntityPaginationParams } from './request/parseEntityPaginationParams';
import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api';
/**
* Options used by {@link createRouter}.
@@ -68,6 +68,8 @@ export interface RouterOptions {
logger: Logger;
config: Config;
permissionIntegrationRouter?: express.Router;
auth: AuthService;
httpAuth: HttpAuthService;
}
/**
@@ -94,6 +96,8 @@ export async function createRouter(
config,
logger,
permissionIntegrationRouter,
auth,
httpAuth,
} = options;
const readonlyEnabled =
@@ -104,12 +108,16 @@ export async function createRouter(
if (refreshService) {
router.post('/refresh', async (req, res) => {
const refreshOptions: RefreshOptions = req.body;
refreshOptions.authorizationToken = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);
const { authorizationToken, ...restBody } = req.body;
await refreshService.refresh(refreshOptions);
const credentials = authorizationToken
? await auth.authenticate(authorizationToken)
: await httpAuth.credentials(req);
await refreshService.refresh({
...restBody,
credentials,
});
res.status(200).end();
});
}
@@ -126,9 +134,7 @@ export async function createRouter(
fields: parseEntityTransformParams(req.query),
order: parseEntityOrderParams(req.query),
pagination: parseEntityPaginationParams(req.query),
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
// Add a Link header to the next page
@@ -147,9 +153,7 @@ export async function createRouter(
await entitiesCatalog.queryEntities({
limit: req.query.limit,
...parseQueryEntitiesParams(req.query),
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.json({
@@ -169,9 +173,7 @@ export async function createRouter(
const { uid } = req.params;
const { entities } = await entitiesCatalog.entities({
filter: basicEntityFilter({ 'metadata.uid': uid }),
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
if (!entities.length) {
throw new NotFoundError(`No entity with uid ${uid}`);
@@ -181,9 +183,7 @@ export async function createRouter(
.delete('/entities/by-uid/:uid', async (req, res) => {
const { uid } = req.params;
await entitiesCatalog.removeEntityByUid(uid, {
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(204).end();
})
@@ -195,9 +195,7 @@ export async function createRouter(
'metadata.namespace': namespace,
'metadata.name': name,
}),
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
if (!entities.length) {
throw new NotFoundError(
@@ -212,22 +210,17 @@ export async function createRouter(
const { kind, namespace, name } = req.params;
const entityRef = stringifyEntityRef({ kind, namespace, name });
const response = await entitiesCatalog.entityAncestry(entityRef, {
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(200).json(response);
},
)
.post('/entities/by-refs', async (req, res) => {
const request = entitiesBatchRequest(req);
const token = getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
);
const response = await entitiesCatalog.entitiesBatch({
entityRefs: request.entityRefs,
fields: parseEntityTransformParams(req.query, request.fields),
authorizationToken: token,
credentials: await httpAuth.credentials(req),
});
res.status(200).json(response);
})
@@ -235,9 +228,7 @@ export async function createRouter(
const response = await entitiesCatalog.facets({
filter: parseEntityFilterParams(req.query),
facets: parseEntityFacetParams(req.query),
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(200).json(response);
});
@@ -256,17 +247,13 @@ export async function createRouter(
}
const output = await locationService.createLocation(location, dryRun, {
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(201).json(output);
})
.get('/locations', async (req, res) => {
const locations = await locationService.listLocations({
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(200).json(locations.map(l => ({ data: l })));
})
@@ -274,9 +261,7 @@ export async function createRouter(
.get('/locations/:id', async (req, res) => {
const { id } = req.params;
const output = await locationService.getLocation(id, {
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(200).json(output);
})
@@ -285,9 +270,7 @@ export async function createRouter(
const { id } = req.params;
await locationService.deleteLocation(id, {
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
credentials: await httpAuth.credentials(req),
});
res.status(204).end();
})
@@ -295,11 +278,7 @@ export async function createRouter(
const { kind, namespace, name } = req.params;
const output = await locationService.getLocationByEntity(
{ kind, namespace, name },
{
authorizationToken: getBearerTokenFromAuthorizationHeader(
req.header('authorization'),
),
},
{ credentials: await httpAuth.credentials(req) },
);
res.status(200).json(output);
});
@@ -28,12 +28,12 @@ import { internal } from '@backstage/backend-openapi-utils';
export function parseQueryEntitiesParams(
params: internal.QuerySchema<typeof spec, '/entities/by-query', 'get'>,
): Omit<QueryEntitiesRequest, 'authorizationToken' | 'limit'> {
): Omit<QueryEntitiesRequest, 'credentials' | 'limit'> {
const fields = parseEntityTransformParams(params);
if (params.cursor) {
const decodedCursor = decodeCursor(params.cursor);
const response: Omit<QueryEntitiesCursorRequest, 'authorizationToken'> = {
const response: Omit<QueryEntitiesCursorRequest, 'credentials'> = {
cursor: decodedCursor,
fields,
};
@@ -43,7 +43,7 @@ export function parseQueryEntitiesParams(
const filter = parseEntityFilterParams(params);
const orderFields = parseEntityOrderFieldParams(params);
const response: Omit<QueryEntitiesInitialRequest, 'authorizationToken'> = {
const response: Omit<QueryEntitiesInitialRequest, 'credentials'> = {
fields,
filter,
orderFields,
+10 -7
View File
@@ -16,6 +16,7 @@
import { CompoundEntityRef, Entity } from '@backstage/catalog-model';
import { Location } from '@backstage/catalog-client';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
/**
* Holds the information required to create a new location in the catalog location store.
@@ -35,22 +36,24 @@ export interface LocationService {
createLocation(
location: LocationInput,
dryRun: boolean,
options?: {
authorizationToken?: string;
options: {
credentials: BackstageCredentials;
},
): Promise<{ location: Location; entities: Entity[]; exists?: boolean }>;
listLocations(options?: { authorizationToken?: string }): Promise<Location[]>;
listLocations(options: {
credentials: BackstageCredentials;
}): Promise<Location[]>;
getLocation(
id: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<Location>;
deleteLocation(
id: string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<void>;
getLocationByEntity(
entityRef: CompoundEntityRef | string,
options?: { authorizationToken?: string },
options: { credentials: BackstageCredentials },
): Promise<Location>;
}
@@ -62,7 +65,7 @@ export interface LocationService {
export type RefreshOptions = {
/** The reference to a single entity that should be refreshed */
entityRef: string;
authorizationToken?: string;
credentials: BackstageCredentials;
};
/**