auth: convert permission-backend to the new auth services

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-02-23 17:12:27 +01:00
parent 59dcc0f2c5
commit 9802004e10
21 changed files with 448 additions and 121 deletions
+13
View File
@@ -12,6 +12,7 @@ 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 { BackstageUserInfo } 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';
@@ -37,6 +38,7 @@ import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
import { TokenManagerService } from '@backstage/backend-plugin-api';
import { UrlReaderService } from '@backstage/backend-plugin-api';
import { UserInfoService } from '@backstage/backend-plugin-api';
// @public
export function createMockDirectory(
@@ -316,6 +318,17 @@ export namespace mockServices {
partialImpl?: Partial<UrlReaderService> | undefined,
) => ServiceMock<UrlReaderService>;
}
export function userInfo(
customInfo?: Partial<BackstageUserInfo>,
): UserInfoService;
// (undocumented)
export namespace userInfo {
const factory: () => ServiceFactory<UserInfoService, 'plugin'>;
const // (undocumented)
mock: (
partialImpl?: Partial<UserInfoService> | undefined,
) => ServiceMock<UserInfoService>;
}
}
// @public
@@ -0,0 +1,55 @@
/*
* 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 { MockUserInfoService } from './MockUserInfoService';
import { mockCredentials } from './mockCredentials';
describe('MockUserInfoService', () => {
it('works without constructor parameters', async () => {
const service = new MockUserInfoService();
const user = mockCredentials.user();
await expect(service.getUserInfo(user)).resolves.toEqual({
userEntityRef: user.principal.userEntityRef,
ownershipEntityRefs: [user.principal.userEntityRef],
});
});
it('works with custom constructor parameters', async () => {
const service = new MockUserInfoService({
userEntityRef: 'user:default/not-the-mock-1',
ownershipEntityRefs: ['user:default/not-the-mock-2'],
});
const user = mockCredentials.user();
await expect(service.getUserInfo(user)).resolves.toEqual({
userEntityRef: 'user:default/not-the-mock-1',
ownershipEntityRefs: ['user:default/not-the-mock-2'],
});
});
it('rejects non-users', async () => {
const service = new MockUserInfoService();
await expect(
service.getUserInfo(mockCredentials.none()),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"User info not available for principal type 'none'"`,
);
await expect(
service.getUserInfo(mockCredentials.service()),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"User info not available for principal type 'service'"`,
);
});
});
@@ -0,0 +1,55 @@
/*
* 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,
BackstageUserInfo,
BackstageUserPrincipal,
UserInfoService,
} from '@backstage/backend-plugin-api';
import { InputError } from '@backstage/errors';
/** @internal */
export class MockUserInfoService implements UserInfoService {
private readonly customInfo: Partial<BackstageUserInfo>;
constructor(customInfo?: Partial<BackstageUserInfo>) {
this.customInfo = customInfo ?? {};
}
async getUserInfo(
credentials: BackstageCredentials,
): Promise<BackstageUserInfo> {
const principal = credentials.principal as
| BackstageUserPrincipal
| BackstageServicePrincipal
| BackstageNonePrincipal;
if (principal.type !== 'user') {
throw new InputError(
`User info not available for principal type '${principal.type}'`,
);
}
return {
userEntityRef: principal.userEntityRef,
ownershipEntityRefs: [principal.userEntityRef],
...this.customInfo,
};
}
}
@@ -27,6 +27,8 @@ import {
DiscoveryService,
HttpAuthService,
BackstageCredentials,
BackstageUserInfo,
UserInfoService,
} from '@backstage/backend-plugin-api';
import {
cacheServiceFactory,
@@ -49,6 +51,7 @@ import { MockRootLoggerService } from './MockRootLoggerService';
import { MockAuthService } from './MockAuthService';
import { MockHttpAuthService } from './MockHttpAuthService';
import { mockCredentials } from './mockCredentials';
import { MockUserInfoService } from './MockUserInfoService';
/** @internal */
function simpleFactory<
@@ -272,6 +275,37 @@ export namespace mockServices {
}));
}
/**
* Creates a mock implementation of the `UserInfoService`.
*
* By default it extracts the user's entity ref from a user principal and
* returns that as the only ownership entity ref, but this can be overridden
* by passing in a custom set of user info.
*/
export function userInfo(
customInfo?: Partial<BackstageUserInfo>,
): UserInfoService {
return new MockUserInfoService(customInfo);
}
export namespace userInfo {
/**
* Creates a mock service factory for the `UserInfoService`.
*
* By default it extracts the user's entity ref from a user principal and
* returns that as the only ownership entity ref.
*/
export const factory = createServiceFactory({
service: coreServices.userInfo,
deps: {},
factory() {
return new MockUserInfoService();
},
});
export const mock = simpleMock(coreServices.userInfo, () => ({
getUserInfo: jest.fn(),
}));
}
// TODO(Rugvip): Not all core services have implementations available here yet.
// some may need a bit more refactoring for it to be simpler to
// re-implement functioning mock versions here.
@@ -284,12 +318,14 @@ export namespace mockServices {
withOptions: jest.fn(),
}));
}
export namespace database {
export const factory = databaseServiceFactory;
export const mock = simpleMock(coreServices.database, () => ({
getClient: jest.fn(),
}));
}
export namespace httpRouter {
export const factory = httpRouterServiceFactory;
export const mock = simpleMock(coreServices.httpRouter, () => ({
@@ -297,12 +333,14 @@ export namespace mockServices {
addAuthPolicy: jest.fn(),
}));
}
export namespace rootHttpRouter {
export const factory = rootHttpRouterServiceFactory;
export const mock = simpleMock(coreServices.rootHttpRouter, () => ({
use: jest.fn(),
}));
}
export namespace lifecycle {
export const factory = lifecycleServiceFactory;
export const mock = simpleMock(coreServices.lifecycle, () => ({
@@ -310,6 +348,7 @@ export namespace mockServices {
addStartupHook: jest.fn(),
}));
}
export namespace logger {
export const factory = loggerServiceFactory;
export const mock = simpleMock(coreServices.logger, () => ({
@@ -320,6 +359,7 @@ export namespace mockServices {
warn: jest.fn(),
}));
}
export namespace permissions {
export const factory = permissionsServiceFactory;
export const mock = simpleMock(coreServices.permissions, () => ({
@@ -327,6 +367,7 @@ export namespace mockServices {
authorizeConditional: jest.fn(),
}));
}
export namespace rootLifecycle {
export const factory = rootLifecycleServiceFactory;
export const mock = simpleMock(coreServices.rootLifecycle, () => ({
@@ -334,6 +375,7 @@ export namespace mockServices {
addStartupHook: jest.fn(),
}));
}
export namespace scheduler {
export const factory = schedulerServiceFactory;
export const mock = simpleMock(coreServices.scheduler, () => ({
@@ -343,6 +385,7 @@ export namespace mockServices {
triggerTask: jest.fn(),
}));
}
export namespace urlReader {
export const factory = urlReaderServiceFactory;
export const mock = simpleMock(coreServices.urlReader, () => ({
@@ -80,6 +80,7 @@ export const defaultServiceFactories = [
mockServices.rootLogger.factory(),
mockServices.scheduler.factory(),
mockServices.tokenManager.factory(),
mockServices.userInfo.factory(),
mockServices.urlReader.factory(),
];