Merge pull request #26115 from backstage/rugvip/shim
backend-common: add shims for token manager and identity services
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
The `legacyPlugin` and `makeLegacyPlugin` helpers now provide their own shim implementation of the identity and token manager services, as these services are being removed from the new backend system.
|
||||
@@ -35,6 +35,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { PluginMetadataService } from '@backstage/backend-plugin-api';
|
||||
import { PushResult } from 'isomorphic-git';
|
||||
import { ReadCommitResult } from 'isomorphic-git';
|
||||
import { Request as Request_2 } from 'express';
|
||||
import { RequestHandler } from 'express';
|
||||
import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api';
|
||||
import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api';
|
||||
@@ -321,6 +322,23 @@ export type KubernetesContainerRunnerOptions = {
|
||||
// @public @deprecated (undocumented)
|
||||
export type LegacyCreateRouter<TEnv> = (deps: TEnv) => Promise<RequestHandler>;
|
||||
|
||||
// @public @deprecated
|
||||
export interface LegacyIdentityService {
|
||||
// (undocumented)
|
||||
getIdentity(options: { request: Request_2<unknown> }): Promise<
|
||||
| {
|
||||
expiresInSeconds?: number;
|
||||
token: string;
|
||||
identity: {
|
||||
type: 'user';
|
||||
userEntityRef: string;
|
||||
ownershipEntityRefs: string[];
|
||||
};
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export const legacyPlugin: (
|
||||
name: string,
|
||||
@@ -335,9 +353,7 @@ export const legacyPlugin: (
|
||||
logger: LoggerService;
|
||||
permissions: PermissionsService;
|
||||
scheduler: SchedulerService;
|
||||
tokenManager: TokenManagerService;
|
||||
reader: UrlReaderService;
|
||||
identity: IdentityService;
|
||||
},
|
||||
{
|
||||
logger: (log: LoggerService) => Logger;
|
||||
@@ -345,7 +361,10 @@ export const legacyPlugin: (
|
||||
getClient(options?: CacheServiceOptions | undefined): CacheService;
|
||||
};
|
||||
}
|
||||
>
|
||||
> & {
|
||||
tokenManager: TokenManager;
|
||||
identity: LegacyIdentityService;
|
||||
}
|
||||
>;
|
||||
}>,
|
||||
) => BackendFeature;
|
||||
@@ -384,7 +403,12 @@ export function makeLegacyPlugin<
|
||||
): (
|
||||
name: string,
|
||||
createRouterImport: Promise<{
|
||||
default: LegacyCreateRouter<TransformedEnv<TEnv, TEnvTransforms>>;
|
||||
default: LegacyCreateRouter<
|
||||
TransformedEnv<TEnv, TEnvTransforms> & {
|
||||
tokenManager: TokenManager;
|
||||
identity: LegacyIdentityService;
|
||||
}
|
||||
>;
|
||||
}>,
|
||||
) => BackendFeature;
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
*/
|
||||
|
||||
export { legacyPlugin, makeLegacyPlugin } from './legacy';
|
||||
export type { LegacyCreateRouter } from './legacy';
|
||||
export type { LegacyCreateRouter, LegacyIdentityService } from './legacy';
|
||||
|
||||
@@ -18,15 +18,18 @@ import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import {
|
||||
mockCredentials,
|
||||
mockServices,
|
||||
startTestBackend,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Router } from 'express';
|
||||
import request from 'supertest';
|
||||
import { createLegacyAuthAdapters } from '..';
|
||||
import { legacyPlugin } from './legacy';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { authServiceFactory } from '../../../../backend-defaults/src/entrypoints/auth';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { tokenManagerServiceFactory } from '../../../../backend-app-api/src/services/implementations/tokenManager';
|
||||
|
||||
describe('legacyPlugin', () => {
|
||||
it('can auth across the new and old systems', async () => {
|
||||
@@ -41,7 +44,6 @@ describe('legacyPlugin', () => {
|
||||
await startTestBackend({
|
||||
features: [
|
||||
authServiceFactory,
|
||||
tokenManagerServiceFactory,
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
@@ -115,4 +117,54 @@ describe('legacyPlugin', () => {
|
||||
|
||||
await done;
|
||||
});
|
||||
|
||||
it('can auth users with the identity service shim', async () => {
|
||||
const backend = await startTestBackend({
|
||||
features: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
auth: {
|
||||
keys: [
|
||||
{
|
||||
secret: 'test',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
legacyPlugin(
|
||||
'test',
|
||||
Promise.resolve({
|
||||
async default({ identity }) {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const user = await identity.getIdentity({ request: req });
|
||||
res.json(user);
|
||||
});
|
||||
|
||||
return router;
|
||||
},
|
||||
}),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const res = await request(backend.server)
|
||||
.get('/api/test')
|
||||
.set('authorization', mockCredentials.user.header());
|
||||
|
||||
const mockUserRef = mockCredentials.user().principal.userEntityRef;
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
token: mockCredentials.user.token(),
|
||||
identity: {
|
||||
type: 'user',
|
||||
userEntityRef: mockUserRef,
|
||||
ownershipEntityRefs: [mockUserRef],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,13 +18,16 @@ import {
|
||||
AuthService,
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
HttpRouterService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
ServiceRef,
|
||||
UserInfoService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { RequestHandler } from 'express';
|
||||
import { cacheToPluginCacheManager } from '../cache';
|
||||
import { loggerToWinstonLogger } from '../logging';
|
||||
import { TokenManager } from '../../deprecated';
|
||||
import { ServerTokenManager, TokenManager } from '../../deprecated';
|
||||
import { Request } from 'express';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -46,8 +49,13 @@ type TransformedEnv<
|
||||
// new plugin tokens, which we'll also be signaling by supporting the JWKS endpoint through
|
||||
// the http router.
|
||||
// This makes sure that we accept the new plugin tokens as valid tokens, but otherwise fall
|
||||
// back to whatever the token manager is doing.
|
||||
function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) {
|
||||
// back to the legacy token manager.
|
||||
function createTokenManagerShim(
|
||||
auth: AuthService,
|
||||
config: RootConfigService,
|
||||
logger: LoggerService,
|
||||
): TokenManager {
|
||||
const tokenManager = ServerTokenManager.fromConfig(config, { logger });
|
||||
return {
|
||||
async getToken() {
|
||||
return tokenManager.getToken();
|
||||
@@ -64,7 +72,64 @@ function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) {
|
||||
}
|
||||
await tokenManager.authenticate(token);
|
||||
},
|
||||
} satisfies TokenManager;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Originally IdentityApi from `@backstage/plugin-auth-node`, re-declared here for backwards compatibility
|
||||
* @public
|
||||
* @deprecated Only relevant for legacy plugins, which are deprecated.
|
||||
*/
|
||||
export interface LegacyIdentityService {
|
||||
getIdentity(options: { request: Request<unknown> }): Promise<
|
||||
| {
|
||||
expiresInSeconds?: number;
|
||||
|
||||
token: string;
|
||||
identity: {
|
||||
type: 'user';
|
||||
userEntityRef: string;
|
||||
ownershipEntityRefs: string[];
|
||||
};
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
}
|
||||
|
||||
// This doesn't use DefaultIdentityClient because we will be removing it and break support for ownershipEntityRefs
|
||||
function createIdentityServiceShim(
|
||||
auth: AuthService,
|
||||
userInfo: UserInfoService,
|
||||
): LegacyIdentityService {
|
||||
return {
|
||||
async getIdentity(options) {
|
||||
const authHeader = options.request.headers.authorization;
|
||||
if (typeof authHeader !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const token = authHeader.match(/^Bearer[ ]+(\S+)$/i)?.[1];
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const credentials = await auth.authenticate(token);
|
||||
if (!auth.isPrincipal(credentials, 'user')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const info = await userInfo.getUserInfo(credentials);
|
||||
|
||||
return {
|
||||
token,
|
||||
identity: {
|
||||
type: 'user',
|
||||
userEntityRef: info.userEntityRef,
|
||||
ownershipEntityRefs: info.ownershipEntityRefs,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +152,12 @@ export function makeLegacyPlugin<
|
||||
return (
|
||||
name: string,
|
||||
createRouterImport: Promise<{
|
||||
default: LegacyCreateRouter<TransformedEnv<TEnv, TEnvTransforms>>;
|
||||
default: LegacyCreateRouter<
|
||||
TransformedEnv<TEnv, TEnvTransforms> & {
|
||||
tokenManager: TokenManager;
|
||||
identity: LegacyIdentityService;
|
||||
}
|
||||
>;
|
||||
}>,
|
||||
) => {
|
||||
return createBackendPlugin({
|
||||
@@ -96,10 +166,20 @@ export function makeLegacyPlugin<
|
||||
env.registerInit({
|
||||
deps: {
|
||||
...envMapping,
|
||||
_router: coreServices.httpRouter,
|
||||
_auth: coreServices.auth,
|
||||
$$router: coreServices.httpRouter,
|
||||
$$auth: coreServices.auth,
|
||||
$$userInfo: coreServices.userInfo,
|
||||
$$config: coreServices.rootConfig,
|
||||
$$logger: coreServices.logger,
|
||||
},
|
||||
async init({ _router, _auth, ...envDeps }) {
|
||||
async init({
|
||||
$$auth,
|
||||
$$config,
|
||||
$$logger,
|
||||
$$router,
|
||||
$$userInfo,
|
||||
...envDeps
|
||||
}) {
|
||||
const { default: createRouter } = await createRouterImport;
|
||||
const pluginEnv = Object.fromEntries(
|
||||
Object.entries(envDeps).map(([key, dep]) => {
|
||||
@@ -107,19 +187,31 @@ export function makeLegacyPlugin<
|
||||
if (transform) {
|
||||
return [key, transform(dep)];
|
||||
}
|
||||
if (key === 'tokenManager') {
|
||||
return [
|
||||
key,
|
||||
wrapTokenManager(dep as TokenManager, _auth as AuthService),
|
||||
];
|
||||
}
|
||||
return [key, dep];
|
||||
}),
|
||||
);
|
||||
const router = await createRouter(
|
||||
pluginEnv as TransformedEnv<TEnv, TEnvTransforms>,
|
||||
|
||||
const auth = $$auth as typeof coreServices.auth.T;
|
||||
const config = $$config as typeof coreServices.rootConfig.T;
|
||||
const logger = $$logger as typeof coreServices.logger.T;
|
||||
const router = $$router as typeof coreServices.httpRouter.T;
|
||||
const userInfo = $$userInfo as typeof coreServices.userInfo.T;
|
||||
|
||||
// Token manager and identity services are no longer supported in the new backend system, so we provide shims for them.
|
||||
pluginEnv.tokenManager = createTokenManagerShim(
|
||||
auth,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
(_router as HttpRouterService).use(router);
|
||||
pluginEnv.identity = createIdentityServiceShim(auth, userInfo);
|
||||
|
||||
const pluginRouter = await createRouter(
|
||||
pluginEnv as TransformedEnv<TEnv, TEnvTransforms> & {
|
||||
tokenManager: TokenManager;
|
||||
identity: LegacyIdentityService;
|
||||
},
|
||||
);
|
||||
router.use(pluginRouter);
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -153,9 +245,7 @@ export const legacyPlugin = makeLegacyPlugin(
|
||||
logger: coreServices.logger,
|
||||
permissions: coreServices.permissions,
|
||||
scheduler: coreServices.scheduler,
|
||||
tokenManager: coreServices.tokenManager,
|
||||
reader: coreServices.urlReader,
|
||||
identity: coreServices.identity,
|
||||
},
|
||||
{
|
||||
logger: log => loggerToWinstonLogger(log),
|
||||
|
||||
Reference in New Issue
Block a user