backend-test-utils: add default credentials for mock http auth + explicit none credentials header
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -19,7 +19,7 @@ import { MockHttpAuthService } from './MockHttpAuthService';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
|
||||
describe('MockHttpAuthService', () => {
|
||||
const httpAuth = new MockHttpAuthService('test');
|
||||
const httpAuth = new MockHttpAuthService('test', mockCredentials.none());
|
||||
|
||||
function makeAuthReq(header?: string) {
|
||||
return { headers: { authorization: header } } as Request;
|
||||
@@ -93,6 +93,54 @@ describe('MockHttpAuthService', () => {
|
||||
).rejects.toThrow("This endpoint does not allow 'service' credentials");
|
||||
});
|
||||
|
||||
it('should default to different credential types', async () => {
|
||||
const noneAuth = new MockHttpAuthService('test', mockCredentials.none());
|
||||
const userAuth = new MockHttpAuthService('test', mockCredentials.user());
|
||||
const serviceAuth = new MockHttpAuthService(
|
||||
'test',
|
||||
mockCredentials.service(),
|
||||
);
|
||||
|
||||
await expect(noneAuth.credentials(makeAuthReq())).resolves.toEqual(
|
||||
mockCredentials.none(),
|
||||
);
|
||||
await expect(
|
||||
noneAuth.credentials(makeAuthReq(mockCredentials.none.header())),
|
||||
).resolves.toEqual(mockCredentials.none());
|
||||
await expect(
|
||||
noneAuth.credentials(makeAuthReq(mockCredentials.user.header())),
|
||||
).resolves.toEqual(mockCredentials.user());
|
||||
await expect(
|
||||
noneAuth.credentials(makeAuthReq(mockCredentials.service.header())),
|
||||
).resolves.toEqual(mockCredentials.service());
|
||||
|
||||
await expect(userAuth.credentials(makeAuthReq())).resolves.toEqual(
|
||||
mockCredentials.user(),
|
||||
);
|
||||
await expect(
|
||||
userAuth.credentials(makeAuthReq(mockCredentials.none.header())),
|
||||
).resolves.toEqual(mockCredentials.none());
|
||||
await expect(
|
||||
userAuth.credentials(makeAuthReq(mockCredentials.user.header())),
|
||||
).resolves.toEqual(mockCredentials.user());
|
||||
await expect(
|
||||
userAuth.credentials(makeAuthReq(mockCredentials.service.header())),
|
||||
).resolves.toEqual(mockCredentials.service());
|
||||
|
||||
await expect(serviceAuth.credentials(makeAuthReq())).resolves.toEqual(
|
||||
mockCredentials.service(),
|
||||
);
|
||||
await expect(
|
||||
serviceAuth.credentials(makeAuthReq(mockCredentials.none.header())),
|
||||
).resolves.toEqual(mockCredentials.none());
|
||||
await expect(
|
||||
serviceAuth.credentials(makeAuthReq(mockCredentials.user.header())),
|
||||
).resolves.toEqual(mockCredentials.user());
|
||||
await expect(
|
||||
serviceAuth.credentials(makeAuthReq(mockCredentials.service.header())),
|
||||
).resolves.toEqual(mockCredentials.service());
|
||||
});
|
||||
|
||||
it('should reject invalid credentials', async () => {
|
||||
await expect(
|
||||
httpAuth.credentials(makeAuthReq('Bearer bad')),
|
||||
|
||||
@@ -21,26 +21,34 @@ import {
|
||||
HttpAuthService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { Request, Response } from 'express';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
import { MockAuthService } from './MockAuthService';
|
||||
import { NotAllowedError, NotImplementedError } from '@backstage/errors';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
|
||||
// TODO: support mock cookie auth?
|
||||
export class MockHttpAuthService implements HttpAuthService {
|
||||
#auth: AuthService;
|
||||
#defaultCredentials: BackstageCredentials;
|
||||
|
||||
constructor(pluginId: string) {
|
||||
constructor(pluginId: string, defaultCredentials: BackstageCredentials) {
|
||||
this.#auth = new MockAuthService(pluginId);
|
||||
this.#defaultCredentials = defaultCredentials;
|
||||
}
|
||||
|
||||
async #getCredentials(req: Request) {
|
||||
const header = req.headers.authorization;
|
||||
|
||||
if (header === mockCredentials.none.header()) {
|
||||
return mockCredentials.none();
|
||||
}
|
||||
|
||||
const token =
|
||||
typeof header === 'string'
|
||||
? header.match(/^Bearer[ ]+(\S+)$/i)?.[1]
|
||||
: undefined;
|
||||
|
||||
if (!token) {
|
||||
return mockCredentials.none();
|
||||
return this.#defaultCredentials;
|
||||
}
|
||||
|
||||
return await this.#auth.authenticate(token);
|
||||
|
||||
@@ -48,6 +48,10 @@ describe('mockCredentials', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates unauthenticated tokens and headers', () => {
|
||||
expect(mockCredentials.none.header()).toBe('Bearer mock-none-token');
|
||||
});
|
||||
|
||||
it('creates user tokens and headers', () => {
|
||||
expect(mockCredentials.user.token()).toBe('mock-user-token');
|
||||
expect(mockCredentials.user.token('user:default/other')).toBe(
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
export const DEFAULT_MOCK_USER_ENTITY_REF = 'user:default/mock';
|
||||
export const DEFAULT_MOCK_SERVICE_SUBJECT = 'external:test-service';
|
||||
|
||||
export const MOCK_NONE_TOKEN = 'mock-none-token';
|
||||
export const MOCK_USER_TOKEN = 'mock-user-token';
|
||||
export const MOCK_USER_TOKEN_PREFIX = 'mock-user-token:';
|
||||
export const MOCK_INVALID_USER_TOKEN = 'mock-invalid-user-token';
|
||||
@@ -53,6 +54,25 @@ export namespace mockCredentials {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities related to none credentials.
|
||||
*/
|
||||
export namespace none {
|
||||
/**
|
||||
* Returns an authorization header that translates to unauthenticated
|
||||
* credentials.
|
||||
*
|
||||
* This is useful when one wants to explicitly test unauthenticated requests
|
||||
* while still using the default behavior of the mock HttpAuthService where
|
||||
* it defaults to user credentials.
|
||||
*/
|
||||
export function header(): string {
|
||||
// NOTE: there is no .token() version of this because only the
|
||||
// HttpAuthService should know about and consume this token
|
||||
return `Bearer ${MOCK_NONE_TOKEN}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mocked credentials object for a user principal.
|
||||
*
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
AuthService,
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
BackstageCredentials,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
cacheServiceFactory,
|
||||
@@ -47,6 +48,7 @@ import { MockIdentityService } from './MockIdentityService';
|
||||
import { MockRootLoggerService } from './MockRootLoggerService';
|
||||
import { MockAuthService } from './MockAuthService';
|
||||
import { MockHttpAuthService } from './MockHttpAuthService';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
|
||||
/** @internal */
|
||||
function simpleFactory<
|
||||
@@ -203,15 +205,48 @@ export namespace mockServices {
|
||||
}));
|
||||
}
|
||||
|
||||
export function httpAuth(options?: { pluginId?: string }): HttpAuthService {
|
||||
return new MockHttpAuthService(options?.pluginId ?? 'test');
|
||||
/**
|
||||
* Creates a mock implementation of the `HttpAuthService`.
|
||||
*
|
||||
* By default all requests without credentials are treated as requests from
|
||||
* the default mock user principal. This behavior can be configured with the
|
||||
* `defaultCredentials` option.
|
||||
*/
|
||||
export function httpAuth(options?: {
|
||||
pluginId?: string;
|
||||
/**
|
||||
* The default credentials to use if there are no credentials present in the
|
||||
* incoming request.
|
||||
*
|
||||
* By default all requests without credentials are treated as authenticated
|
||||
* as the default mock user as returned from `mockCredentials.user()`.
|
||||
*/
|
||||
defaultCredentials?: BackstageCredentials;
|
||||
}): HttpAuthService {
|
||||
return new MockHttpAuthService(
|
||||
options?.pluginId ?? 'test',
|
||||
options?.defaultCredentials ?? mockCredentials.user(),
|
||||
);
|
||||
}
|
||||
export namespace httpAuth {
|
||||
export const factory = createServiceFactory({
|
||||
service: coreServices.httpAuth,
|
||||
deps: { plugin: coreServices.pluginMetadata },
|
||||
factory: ({ plugin }) => new MockHttpAuthService(plugin.getId()),
|
||||
});
|
||||
/**
|
||||
* Creates a mock service factory for the `HttpAuthService`.
|
||||
*
|
||||
* By default all requests without credentials are treated as requests from
|
||||
* the default mock user principal. This behavior can be configured with the
|
||||
* `defaultCredentials` option.
|
||||
*/
|
||||
export const factory = createServiceFactory(
|
||||
(options?: { defaultCredentials?: BackstageCredentials }) => ({
|
||||
service: coreServices.httpAuth,
|
||||
deps: { plugin: coreServices.pluginMetadata },
|
||||
factory: ({ plugin }) =>
|
||||
new MockHttpAuthService(
|
||||
plugin.getId(),
|
||||
options?.defaultCredentials ?? mockCredentials.user(),
|
||||
),
|
||||
}),
|
||||
);
|
||||
export const mock = simpleMock(coreServices.httpAuth, () => ({
|
||||
credentials: jest.fn(),
|
||||
issueUserCookie: jest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user