backend-test-utils: refactor mock service auth tokens to mirror creation options
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -49,15 +49,22 @@ export function isDockerDisabledForTests(): boolean;
|
||||
// @public (undocumented)
|
||||
export namespace mockCredentials {
|
||||
export function none(): BackstageCredentials<BackstageNonePrincipal>;
|
||||
export namespace none {
|
||||
export function header(): string;
|
||||
}
|
||||
export function service(
|
||||
subject?: string,
|
||||
): BackstageCredentials<BackstageServicePrincipal>;
|
||||
export namespace service {
|
||||
export function header(payload?: TokenPayload): string;
|
||||
export function token(payload?: TokenPayload): string;
|
||||
export type TokenPayload = {
|
||||
subject?: string;
|
||||
targetPluginId?: string;
|
||||
export function header(options?: TokenOptions): string;
|
||||
// (undocumented)
|
||||
export function invalidHeader(): string;
|
||||
// (undocumented)
|
||||
export function invalidToken(): string;
|
||||
export function token(options?: TokenOptions): string;
|
||||
export type TokenOptions = {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
targetPluginId: string;
|
||||
};
|
||||
}
|
||||
export function user(
|
||||
@@ -65,10 +72,11 @@ export namespace mockCredentials {
|
||||
): BackstageCredentials<BackstageUserPrincipal>;
|
||||
export namespace user {
|
||||
export function header(userEntityRef?: string): string;
|
||||
// (undocumented)
|
||||
export function invalidHeader(): string;
|
||||
// (undocumented)
|
||||
export function invalidToken(): string;
|
||||
export function token(userEntityRef?: string): string;
|
||||
export type TokenPayload = {
|
||||
userEntityRef?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,12 +167,19 @@ export namespace mockServices {
|
||||
partialImpl?: Partial<DiscoveryService> | undefined,
|
||||
) => ServiceMock<DiscoveryService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export function httpAuth(options?: { pluginId?: string }): HttpAuthService;
|
||||
export function httpAuth(options?: {
|
||||
pluginId?: string;
|
||||
defaultCredentials?: BackstageCredentials;
|
||||
}): HttpAuthService;
|
||||
// (undocumented)
|
||||
export namespace httpAuth {
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<HttpAuthService, 'plugin'>;
|
||||
const factory: (
|
||||
options?:
|
||||
| {
|
||||
defaultCredentials?: BackstageCredentials | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ServiceFactory<HttpAuthService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<HttpAuthService> | undefined,
|
||||
|
||||
@@ -72,23 +72,32 @@ describe('MockAuthService', () => {
|
||||
auth.authenticate(mockCredentials.service.token()),
|
||||
).resolves.toEqual(mockCredentials.service(DEFAULT_MOCK_SERVICE_SUBJECT));
|
||||
|
||||
await expect(
|
||||
auth.authenticate(mockCredentials.service.token()),
|
||||
).resolves.toEqual(mockCredentials.service());
|
||||
|
||||
await expect(
|
||||
auth.authenticate(
|
||||
mockCredentials.service.token({ subject: 'plugin:catalog' }),
|
||||
mockCredentials.service.token({
|
||||
onBehalfOf: mockCredentials.service('plugin:catalog'),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
),
|
||||
).resolves.toEqual(mockCredentials.service('plugin:catalog'));
|
||||
|
||||
await expect(
|
||||
auth.authenticate(
|
||||
mockCredentials.service.token({
|
||||
onBehalfOf: await auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
),
|
||||
).resolves.toEqual(mockCredentials.service());
|
||||
).resolves.toEqual(mockCredentials.service('plugin:test'));
|
||||
|
||||
await expect(
|
||||
auth.authenticate(
|
||||
mockCredentials.service.token({
|
||||
onBehalfOf: await auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
),
|
||||
@@ -135,7 +144,12 @@ describe('MockAuthService', () => {
|
||||
onBehalfOf: mockCredentials.user(),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
).resolves.toEqual({ token: mockCredentials.user.token() });
|
||||
).resolves.toEqual({
|
||||
token: mockCredentials.service.token({
|
||||
onBehalfOf: mockCredentials.user(),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
auth.getPluginRequestToken({
|
||||
@@ -143,7 +157,10 @@ describe('MockAuthService', () => {
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
token: mockCredentials.user.token('user:default/other'),
|
||||
token: mockCredentials.service.token({
|
||||
onBehalfOf: mockCredentials.user('user:default/other'),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -153,6 +170,7 @@ describe('MockAuthService', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
token: mockCredentials.service.token({
|
||||
onBehalfOf: mockCredentials.service(),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
});
|
||||
@@ -164,7 +182,7 @@ describe('MockAuthService', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
token: mockCredentials.service.token({
|
||||
subject: 'external:other',
|
||||
onBehalfOf: mockCredentials.service('external:other'),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
});
|
||||
@@ -176,7 +194,7 @@ describe('MockAuthService', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
token: mockCredentials.service.token({
|
||||
subject: 'plugin:test',
|
||||
onBehalfOf: await mockCredentials.service('plugin:test'),
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -29,10 +29,10 @@ import {
|
||||
MOCK_USER_TOKEN_PREFIX,
|
||||
MOCK_SERVICE_TOKEN,
|
||||
MOCK_SERVICE_TOKEN_PREFIX,
|
||||
DEFAULT_MOCK_USER_ENTITY_REF,
|
||||
DEFAULT_MOCK_SERVICE_SUBJECT,
|
||||
MOCK_INVALID_USER_TOKEN,
|
||||
MOCK_INVALID_SERVICE_TOKEN,
|
||||
UserTokenPayload,
|
||||
ServiceTokenPayload,
|
||||
} from './mockCredentials';
|
||||
|
||||
/** @internal */
|
||||
@@ -55,7 +55,7 @@ export class MockAuthService implements AuthService {
|
||||
}
|
||||
|
||||
if (token.startsWith(MOCK_USER_TOKEN_PREFIX)) {
|
||||
const { userEntityRef }: mockCredentials.user.TokenPayload = JSON.parse(
|
||||
const { sub: userEntityRef }: UserTokenPayload = JSON.parse(
|
||||
token.slice(MOCK_USER_TOKEN_PREFIX.length),
|
||||
);
|
||||
|
||||
@@ -63,16 +63,20 @@ export class MockAuthService implements AuthService {
|
||||
}
|
||||
|
||||
if (token.startsWith(MOCK_SERVICE_TOKEN_PREFIX)) {
|
||||
const { targetPluginId, subject }: mockCredentials.service.TokenPayload =
|
||||
JSON.parse(token.slice(MOCK_SERVICE_TOKEN_PREFIX.length));
|
||||
const { sub, target, obo }: ServiceTokenPayload = JSON.parse(
|
||||
token.slice(MOCK_SERVICE_TOKEN_PREFIX.length),
|
||||
);
|
||||
|
||||
if (targetPluginId && targetPluginId !== this.pluginId) {
|
||||
if (target && target !== this.pluginId) {
|
||||
throw new AuthenticationError(
|
||||
`Invalid mock token target plugin ID, got '${targetPluginId}' but expected '${this.pluginId}'`,
|
||||
`Invalid mock token target plugin ID, got '${target}' but expected '${this.pluginId}'`,
|
||||
);
|
||||
}
|
||||
if (obo) {
|
||||
return mockCredentials.user(obo);
|
||||
}
|
||||
|
||||
return mockCredentials.service(subject);
|
||||
return mockCredentials.service(sub);
|
||||
}
|
||||
|
||||
throw new AuthenticationError(`Unknown mock token '${token}'`);
|
||||
@@ -113,28 +117,17 @@ export class MockAuthService implements AuthService {
|
||||
| BackstageServicePrincipal
|
||||
| BackstageNonePrincipal;
|
||||
|
||||
switch (principal.type) {
|
||||
case 'user':
|
||||
if (principal.userEntityRef === DEFAULT_MOCK_USER_ENTITY_REF) {
|
||||
return { token: mockCredentials.user.token() };
|
||||
}
|
||||
return {
|
||||
token: mockCredentials.user.token(principal.userEntityRef),
|
||||
};
|
||||
case 'service':
|
||||
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}'`,
|
||||
);
|
||||
if (principal.type !== 'user' && principal.type !== 'service') {
|
||||
throw new AuthenticationError(
|
||||
`Refused to issue service token for credential type '${principal.type}'`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
token: mockCredentials.service.token({
|
||||
onBehalfOf: options.onBehalfOf,
|
||||
targetPluginId: options.targetPluginId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,10 @@ describe('MockHttpAuthService', () => {
|
||||
await expect(
|
||||
httpAuth.credentials(
|
||||
makeAuthReq(
|
||||
mockCredentials.service.header({ subject: 'plugin:other' }),
|
||||
mockCredentials.service.header({
|
||||
onBehalfOf: mockCredentials.service('plugin:other'),
|
||||
targetPluginId: 'test',
|
||||
}),
|
||||
),
|
||||
),
|
||||
).resolves.toEqual(mockCredentials.service('plugin:other'));
|
||||
|
||||
@@ -55,13 +55,13 @@ describe('mockCredentials', () => {
|
||||
it('creates user tokens and headers', () => {
|
||||
expect(mockCredentials.user.token()).toBe('mock-user-token');
|
||||
expect(mockCredentials.user.token('user:default/other')).toBe(
|
||||
'mock-user-token:{"userEntityRef":"user:default/other"}',
|
||||
'mock-user-token:{"sub":"user:default/other"}',
|
||||
);
|
||||
expect(mockCredentials.user.invalidToken()).toBe('mock-invalid-user-token');
|
||||
|
||||
expect(mockCredentials.user.header()).toBe('Bearer mock-user-token');
|
||||
expect(mockCredentials.user.header('user:default/other')).toBe(
|
||||
'Bearer mock-user-token:{"userEntityRef":"user:default/other"}',
|
||||
'Bearer mock-user-token:{"sub":"user:default/other"}',
|
||||
);
|
||||
expect(mockCredentials.user.invalidHeader()).toBe(
|
||||
'Bearer mock-invalid-user-token',
|
||||
@@ -70,60 +70,38 @@ describe('mockCredentials', () => {
|
||||
|
||||
it('creates service tokens and headers', () => {
|
||||
expect(mockCredentials.service.token()).toBe('mock-service-token');
|
||||
expect(mockCredentials.service.token({ subject: 'external:other' })).toBe(
|
||||
'mock-service-token:{"subject":"external:other"}',
|
||||
);
|
||||
expect(
|
||||
mockCredentials.service.token({
|
||||
onBehalfOf: mockCredentials.service('external:other'),
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
).toBe('mock-service-token:{"targetPluginId":"other"}');
|
||||
).toBe('mock-service-token:{"sub":"external:other","target":"other"}');
|
||||
expect(
|
||||
mockCredentials.service.token({
|
||||
subject: 'external:other',
|
||||
onBehalfOf: mockCredentials.user('user:default/other'),
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
).toBe(
|
||||
'mock-service-token:{"subject":"external:other","targetPluginId":"other"}',
|
||||
);
|
||||
// Object keys are reordered, ordering in the token should be stable
|
||||
expect(
|
||||
mockCredentials.service.token({
|
||||
targetPluginId: 'other',
|
||||
subject: 'external:other',
|
||||
}),
|
||||
).toBe(
|
||||
'mock-service-token:{"subject":"external:other","targetPluginId":"other"}',
|
||||
);
|
||||
).toBe('mock-service-token:{"obo":"user:default/other","target":"other"}');
|
||||
expect(mockCredentials.service.invalidToken()).toBe(
|
||||
'mock-invalid-service-token',
|
||||
);
|
||||
|
||||
expect(mockCredentials.service.header()).toBe('Bearer mock-service-token');
|
||||
expect(mockCredentials.service.header({ subject: 'external:other' })).toBe(
|
||||
'Bearer mock-service-token:{"subject":"external:other"}',
|
||||
);
|
||||
expect(
|
||||
mockCredentials.service.header({
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
).toBe('Bearer mock-service-token:{"targetPluginId":"other"}');
|
||||
expect(
|
||||
mockCredentials.service.header({
|
||||
subject: 'external:other',
|
||||
onBehalfOf: mockCredentials.service('external:other'),
|
||||
targetPluginId: 'other',
|
||||
}),
|
||||
).toBe(
|
||||
'Bearer mock-service-token:{"subject":"external:other","targetPluginId":"other"}',
|
||||
'Bearer mock-service-token:{"sub":"external:other","target":"other"}',
|
||||
);
|
||||
// Object keys are reordered, ordering in the token should be stable
|
||||
expect(
|
||||
mockCredentials.service.header({
|
||||
onBehalfOf: mockCredentials.user('user:default/other'),
|
||||
targetPluginId: 'other',
|
||||
subject: 'external:other',
|
||||
}),
|
||||
).toBe(
|
||||
'Bearer mock-service-token:{"subject":"external:other","targetPluginId":"other"}',
|
||||
'Bearer mock-service-token:{"obo":"user:default/other","target":"other"}',
|
||||
);
|
||||
expect(mockCredentials.service.invalidHeader()).toBe(
|
||||
'Bearer mock-invalid-service-token',
|
||||
|
||||
@@ -40,6 +40,24 @@ function validateUserEntityRef(ref: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The payload that can be encoded into a mock user token.
|
||||
* @internal
|
||||
*/
|
||||
export type UserTokenPayload = {
|
||||
sub?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The payload that can be encoded into a mock service token.
|
||||
* @internal
|
||||
*/
|
||||
export type ServiceTokenPayload = {
|
||||
sub?: string; // service subject
|
||||
obo?: string; // user entity reference
|
||||
target?: string; // target plugin id
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -92,11 +110,6 @@ export namespace mockCredentials {
|
||||
* 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
|
||||
@@ -105,7 +118,9 @@ export namespace mockCredentials {
|
||||
export function token(userEntityRef?: string): string {
|
||||
if (userEntityRef) {
|
||||
validateUserEntityRef(userEntityRef);
|
||||
return `${MOCK_USER_TOKEN_PREFIX}${JSON.stringify({ userEntityRef })}`;
|
||||
return `${MOCK_USER_TOKEN_PREFIX}${JSON.stringify({
|
||||
sub: userEntityRef,
|
||||
} satisfies UserTokenPayload)}`;
|
||||
}
|
||||
return MOCK_USER_TOKEN;
|
||||
}
|
||||
@@ -147,36 +162,47 @@ export namespace mockCredentials {
|
||||
*/
|
||||
export namespace service {
|
||||
/**
|
||||
* The payload that can be encoded into a mock service token.
|
||||
* Options for the creation of mock service tokens.
|
||||
*/
|
||||
export type TokenPayload = {
|
||||
subject?: string;
|
||||
targetPluginId?: string;
|
||||
export type TokenOptions = {
|
||||
onBehalfOf: BackstageCredentials;
|
||||
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.
|
||||
* Creates a mocked service token. The provided options 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) {
|
||||
const { subject, targetPluginId } = payload; // for fixed ordering
|
||||
export function token(options?: TokenOptions): string {
|
||||
if (options) {
|
||||
const { targetPluginId, onBehalfOf } = options; // for fixed ordering
|
||||
|
||||
const oboPrincipal = onBehalfOf?.principal as
|
||||
| BackstageServicePrincipal
|
||||
| BackstageUserPrincipal
|
||||
| BackstageNonePrincipal;
|
||||
const obo =
|
||||
oboPrincipal.type === 'user' ? oboPrincipal.userEntityRef : undefined;
|
||||
const subject =
|
||||
oboPrincipal.type === 'service' ? oboPrincipal.subject : undefined;
|
||||
|
||||
return `${MOCK_SERVICE_TOKEN_PREFIX}${JSON.stringify({
|
||||
subject,
|
||||
targetPluginId,
|
||||
})}`;
|
||||
sub: subject,
|
||||
obo,
|
||||
target: targetPluginId,
|
||||
} satisfies ServiceTokenPayload)}`;
|
||||
}
|
||||
return MOCK_SERVICE_TOKEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an authorization header with 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.
|
||||
* Returns an authorization header with a mocked service token. The provided
|
||||
* options will be encoded into the token and forwarded to the credentials
|
||||
* object when authenticated by the mock auth service.
|
||||
*/
|
||||
export function header(payload?: TokenPayload): string {
|
||||
return `Bearer ${token(payload)}`;
|
||||
export function header(options?: TokenOptions): string {
|
||||
return `Bearer ${token(options)}`;
|
||||
}
|
||||
|
||||
export function invalidToken(): string {
|
||||
|
||||
Reference in New Issue
Block a user