backend-test-utils: add MockServiceAuth tests + fixes

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-16 12:19:09 +01:00
parent ddb7060e08
commit f69b9dadf9
3 changed files with 178 additions and 7 deletions
@@ -0,0 +1,169 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockAuthService } from './MockAuthService';
import {
DEFAULT_MOCK_SERVICE_SUBJECT,
DEFAULT_MOCK_USER_ENTITY_REF,
MOCK_SERVICE_TOKEN_PREFIX,
MOCK_USER_TOKEN_PREFIX,
mockCredentials,
} from './mockCredentials';
describe('MockAuthService', () => {
const auth = new MockAuthService('test');
it('should reject invalid tokens', async () => {
await expect(auth.authenticate('')).rejects.toThrow('Invalid mock token');
await expect(auth.authenticate('not-a-mock-token')).rejects.toThrow(
'Invalid mock token',
);
await expect(auth.authenticate(MOCK_USER_TOKEN_PREFIX)).rejects.toThrow(
'Unexpected end of JSON input',
);
await expect(
auth.authenticate(`${MOCK_USER_TOKEN_PREFIX}{"invalid":json}`),
).rejects.toThrow('Unexpected token');
await expect(auth.authenticate(MOCK_SERVICE_TOKEN_PREFIX)).rejects.toThrow(
'Unexpected end of JSON input',
);
await expect(
auth.authenticate(`${MOCK_SERVICE_TOKEN_PREFIX}{"invalid":json}`),
).rejects.toThrow('Unexpected token');
});
it('should authenticate mock user tokens', async () => {
await expect(
auth.authenticate(mockCredentials.user.token()),
).resolves.toEqual(mockCredentials.user());
await expect(
auth.authenticate(mockCredentials.user.token()),
).resolves.toEqual(mockCredentials.user(DEFAULT_MOCK_USER_ENTITY_REF));
await expect(
auth.authenticate(
mockCredentials.user.token({ userEntityRef: 'user:default/other' }),
),
).resolves.toEqual(mockCredentials.user('user:default/other'));
});
it('should authenticate mock service tokens', async () => {
await expect(
auth.authenticate(mockCredentials.service.token()),
).resolves.toEqual(mockCredentials.service());
await expect(
auth.authenticate(mockCredentials.service.token()),
).resolves.toEqual(mockCredentials.service(DEFAULT_MOCK_SERVICE_SUBJECT));
await expect(
auth.authenticate(
mockCredentials.service.token({ subject: 'plugin:catalog' }),
),
).resolves.toEqual(mockCredentials.service('plugin:catalog'));
await expect(
auth.authenticate(
mockCredentials.service.token({
targetPluginId: 'test',
}),
),
).resolves.toEqual(mockCredentials.service());
await expect(
auth.authenticate(
mockCredentials.service.token({
targetPluginId: 'other',
}),
),
).rejects.toThrow(
"Invalid mock token target plugin ID, got 'other' but expected 'test'",
);
});
it('should return own service credentials', async () => {
await expect(auth.getOwnServiceCredentials()).resolves.toEqual(
mockCredentials.service('plugin:test'),
);
});
it('should check principal types', () => {
const none = mockCredentials.none();
const user = mockCredentials.user();
const service = mockCredentials.service();
expect(auth.isPrincipal(none, 'unknown')).toBe(true);
expect(auth.isPrincipal(user, 'unknown')).toBe(true);
expect(auth.isPrincipal(service, 'unknown')).toBe(true);
expect(auth.isPrincipal(none, 'none')).toBe(true);
expect(auth.isPrincipal(user, 'none')).toBe(false);
expect(auth.isPrincipal(service, 'none')).toBe(false);
expect(auth.isPrincipal(none, 'user')).toBe(false);
expect(auth.isPrincipal(user, 'user')).toBe(true);
expect(auth.isPrincipal(service, 'user')).toBe(false);
expect(auth.isPrincipal(none, 'service')).toBe(false);
expect(auth.isPrincipal(user, 'service')).toBe(false);
expect(auth.isPrincipal(service, 'service')).toBe(true);
});
it('should issue plugin request tokens', async () => {
await expect(
auth.getPluginRequestToken({
onBehalfOf: mockCredentials.user(),
targetPluginId: 'test',
}),
).resolves.toEqual({ token: mockCredentials.user.token() });
await expect(
auth.getPluginRequestToken({
onBehalfOf: mockCredentials.service(),
targetPluginId: 'test',
}),
).resolves.toEqual({
token: mockCredentials.service.token({
targetPluginId: 'test',
}),
});
await expect(
auth.getPluginRequestToken({
onBehalfOf: mockCredentials.service('external:other'),
targetPluginId: 'test',
}),
).resolves.toEqual({
token: mockCredentials.service.token({
subject: 'external:other',
targetPluginId: 'test',
}),
});
await expect(
auth.getPluginRequestToken({
onBehalfOf: await auth.getOwnServiceCredentials(),
targetPluginId: 'other',
}),
).resolves.toEqual({
token: mockCredentials.service.token({
subject: 'plugin:test',
targetPluginId: 'other',
}),
});
});
});
@@ -59,7 +59,7 @@ export class MockAuthService implements AuthService {
if (targetPluginId && targetPluginId !== this.pluginId) {
throw new AuthenticationError(
`Invalid mock token target, got ${targetPluginId} but expected ${this.pluginId}`,
`Invalid mock token target plugin ID, got '${targetPluginId}' but expected '${this.pluginId}'`,
);
}
@@ -72,10 +72,7 @@ export class MockAuthService implements AuthService {
async getOwnServiceCredentials(): Promise<
BackstageCredentials<BackstageServicePrincipal>
> {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'service', subject: `plugin:${this.pluginId}` },
};
return mockCredentials.service(`plugin:${this.pluginId}`);
}
isPrincipal<TType extends keyof BackstagePrincipalTypes>(
@@ -73,7 +73,8 @@ export namespace mockCredentials {
*/
export function token(payload?: TokenPayload): string {
if (payload) {
return `${MOCK_USER_TOKEN_PREFIX}:${JSON.stringify(payload)}`;
const { userEntityRef } = payload; // for fixed ordering
return `${MOCK_USER_TOKEN_PREFIX}${JSON.stringify({ userEntityRef })}`;
}
return MOCK_USER_TOKEN;
}
@@ -112,7 +113,11 @@ export namespace mockCredentials {
*/
export function token(payload?: TokenPayload): string {
if (payload) {
return `${MOCK_SERVICE_TOKEN_PREFIX}:${payload}`;
const { subject, targetPluginId } = payload; // for fixed ordering
return `${MOCK_SERVICE_TOKEN_PREFIX}${JSON.stringify({
subject,
targetPluginId,
})}`;
}
return MOCK_SERVICE_TOKEN;
}