backend-plugin-api,permission-node: remove deprecated token option from permissions service

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-07-12 12:17:39 +02:00
parent f4085b87f5
commit 36f91e8956
6 changed files with 77 additions and 209 deletions
+5
View File
@@ -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.
+5
View File
@@ -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.
+8 -9
View File
@@ -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<AuthorizePermissionResponse[]>;
authorizeConditional(
requests: QueryPermissionRequest[],
options?: PermissionsServiceRequestOptions,
options: PermissionsServiceRequestOptions,
): Promise<QueryPermissionResponse[]>;
}
// @public
export type PermissionsServiceRequestOptions =
| {
token?: string;
}
| {
credentials: BackstageCredentials;
};
export interface PermissionsServiceRequestOptions
extends EvaluatorRequestOptions {
// (undocumented)
credentials: BackstageCredentials;
}
// @public
export interface PluginMetadataService {
@@ -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<AuthorizePermissionResponse[]>;
/**
@@ -79,6 +76,6 @@ export interface PermissionsService extends PermissionEvaluator {
*/
authorizeConditional(
requests: QueryPermissionRequest[],
options?: PermissionsServiceRequestOptions,
options: PermissionsServiceRequestOptions,
): Promise<QueryPermissionResponse[]>;
}
@@ -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<DefinitivePolicyDecision>) => ({
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<ConditionalPolicyDecision>) => ({
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',
}),
);
});
});
@@ -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;
}