backend-test-utils: add mockCredentials and use them to implement MockAuthService

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-15 19:48:19 +01:00
parent f52a439239
commit de0244f538
4 changed files with 202 additions and 13 deletions
+28
View File
@@ -9,6 +9,10 @@
import { AuthService } from '@backstage/backend-plugin-api';
import { Backend } from '@backstage/backend-app-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { BackstageNonePrincipal } from '@backstage/backend-plugin-api';
import { BackstageServicePrincipal } from '@backstage/backend-plugin-api';
import { BackstageUserPrincipal } from '@backstage/backend-plugin-api';
import { CacheService } from '@backstage/backend-plugin-api';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
@@ -42,6 +46,30 @@ export function createMockDirectory(
// @public (undocumented)
export function isDockerDisabledForTests(): boolean;
// @public (undocumented)
export namespace mockCredentials {
export function none(): BackstageCredentials<BackstageNonePrincipal>;
export function service(
subject?: string,
): BackstageCredentials<BackstageServicePrincipal>;
export namespace service {
export function token(payload?: TokenPayload): string;
export type TokenPayload = {
subject?: string;
targetPluginId?: string;
};
}
export function user(
userEntityRef?: string,
): BackstageCredentials<BackstageUserPrincipal>;
export namespace user {
export function token(payload?: TokenPayload): string;
export type TokenPayload = {
userEntityRef: string;
};
}
}
// @public
export interface MockDirectory {
addContent(root: MockDirectoryContent): void;
@@ -23,25 +23,50 @@ import {
AuthService,
} from '@backstage/backend-plugin-api';
import { AuthenticationError } from '@backstage/errors';
import {
mockCredentials,
MOCK_USER_TOKEN,
MOCK_USER_TOKEN_PREFIX,
MOCK_SERVICE_TOKEN,
MOCK_SERVICE_TOKEN_PREFIX,
DEFAULT_MOCK_USER_ENTITY_REF,
DEFAULT_MOCK_SERVICE_SUBJECT,
} from './mockCredentials';
/** @internal */
export class MockAuthService implements AuthService {
constructor(private readonly pluginId: string) {}
async authenticate(token: string): Promise<BackstageCredentials> {
if (token === 'mock-user-token') {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'user', userEntityRef: 'user:default/mock' },
};
} else if (token === 'mock-service-token') {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'service', subject: 'external:test-service' },
};
if (token === MOCK_USER_TOKEN) {
return mockCredentials.user();
}
if (token === MOCK_SERVICE_TOKEN) {
return mockCredentials.service();
}
throw new AuthenticationError('Invalid token');
if (token.startsWith(MOCK_USER_TOKEN_PREFIX)) {
const { userEntityRef }: mockCredentials.user.TokenPayload = JSON.parse(
token.slice(MOCK_USER_TOKEN_PREFIX.length),
);
return mockCredentials.user(userEntityRef);
}
if (token.startsWith(MOCK_SERVICE_TOKEN_PREFIX)) {
const { targetPluginId, subject }: mockCredentials.service.TokenPayload =
JSON.parse(token.slice(MOCK_SERVICE_TOKEN_PREFIX.length));
if (targetPluginId && targetPluginId !== this.pluginId) {
throw new AuthenticationError(
`Invalid mock token target, got ${targetPluginId} but expected ${this.pluginId}`,
);
}
return mockCredentials.service(subject);
}
throw new AuthenticationError('Invalid mock token');
}
async getOwnServiceCredentials(): Promise<
@@ -79,9 +104,24 @@ export class MockAuthService implements AuthService {
switch (principal.type) {
case 'user':
return { token: 'mock-user-token' };
if (principal.userEntityRef === DEFAULT_MOCK_USER_ENTITY_REF) {
return { token: mockCredentials.user.token() };
}
return {
token: mockCredentials.user.token({
userEntityRef: principal.userEntityRef,
}),
};
case 'service':
return { token: `mock-service-token-for-${options.targetPluginId}` };
return {
token: mockCredentials.service.token({
targetPluginId: options.targetPluginId,
subject:
principal.subject === DEFAULT_MOCK_SERVICE_SUBJECT
? undefined
: principal.subject,
}),
};
default:
throw new AuthenticationError(
`Refused to issue service token for credential type '${principal.type}'`,
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { mockServices, type ServiceMock } from './mockServices';
export { mockCredentials } from './mockCredentials';
@@ -0,0 +1,120 @@
/*
* 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 {
BackstageCredentials,
BackstageNonePrincipal,
BackstageServicePrincipal,
BackstageUserPrincipal,
} from '@backstage/backend-plugin-api';
export const DEFAULT_MOCK_USER_ENTITY_REF = 'user:default/mock';
export const DEFAULT_MOCK_SERVICE_SUBJECT = 'external:test-service';
export const MOCK_USER_TOKEN = 'mock-user-token';
export const MOCK_USER_TOKEN_PREFIX = 'mock-user-token:';
export const MOCK_SERVICE_TOKEN = 'mock-service-token';
export const MOCK_SERVICE_TOKEN_PREFIX = 'mock-service-token:';
/**
* @public
*/
export namespace mockCredentials {
/**
* Creates a mocked credentials object for a unauthenticated principal.
*/
export function none(): BackstageCredentials<BackstageNonePrincipal> {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'none' },
};
}
/**
* Creates a mocked credentials object for a user principal.
*
* The default user entity reference is 'user:default/mock'.
*/
export function user(
userEntityRef: string = DEFAULT_MOCK_USER_ENTITY_REF,
): BackstageCredentials<BackstageUserPrincipal> {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'user', userEntityRef },
};
}
/**
* Utilities related to user credentials.
*/
export namespace user {
/**
* The payload that can be encoded into a mock user token.
*/
export type TokenPayload = { userEntityRef?: string };
/**
* Creates a mocked user token. If a payload is provided it will be encoded
* into the token and forwarded to the credentials object when authenticated
* by the mock auth service.
*/
export function token(payload?: TokenPayload): string {
if (payload) {
return `${MOCK_USER_TOKEN_PREFIX}:${JSON.stringify(payload)}`;
}
return MOCK_USER_TOKEN;
}
}
/**
* Creates a mocked credentials object for a service principal.
*
* The default subject is 'external:test-service'.
*/
export function service(
subject: string = DEFAULT_MOCK_SERVICE_SUBJECT,
): BackstageCredentials<BackstageServicePrincipal> {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'service', subject },
};
}
/**
* Utilities related to service credentials.
*/
export namespace service {
/**
* The payload that can be encoded into a mock service token.
*/
export type TokenPayload = {
subject?: string;
targetPluginId?: string;
};
/**
* Creates a mocked service token. If a payload is provided it will be
* encoded into the token and forwarded to the credentials object when
* authenticated by the mock auth service.
*/
export function token(payload?: TokenPayload): string {
if (payload) {
return `${MOCK_SERVICE_TOKEN_PREFIX}:${payload}`;
}
return MOCK_SERVICE_TOKEN;
}
}
}