Use server token for catalog identity client

Signed-off-by: Joe Porpeglia <josephp@spotify.com>
This commit is contained in:
Joe Porpeglia
2022-01-26 16:31:33 -05:00
parent e628945d4b
commit ae1bba9ebc
22 changed files with 96 additions and 61 deletions
+8 -1
View File
@@ -23,6 +23,13 @@ export default async function createPlugin({
database,
config,
discovery,
tokenManager,
}: PluginEnvironment): Promise<Router> {
return await createRouter({ logger, config, database, discovery });
return await createRouter({
logger,
config,
database,
discovery,
tokenManager,
});
}
@@ -7,6 +7,7 @@ export default async function createPlugin({
database,
config,
discovery,
tokenManager,
}: PluginEnvironment): Promise<Router> {
return await createRouter({ logger, config, database, discovery });
return await createRouter({ logger, config, database, discovery, tokenManager });
}
@@ -14,13 +14,13 @@
* limitations under the License.
*/
import { TokenManager } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import {
RELATION_MEMBER_OF,
UserEntity,
UserEntityV1alpha1,
} from '@backstage/catalog-model';
import { TokenIssuer } from '../../identity';
import { CatalogIdentityClient } from './CatalogIdentityClient';
describe('CatalogIdentityClient', () => {
@@ -36,19 +36,19 @@ describe('CatalogIdentityClient', () => {
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
};
const tokenIssuer: jest.Mocked<TokenIssuer> = {
issueToken: jest.fn(),
listPublicKeys: jest.fn(),
const tokenManager: jest.Mocked<TokenManager> = {
getToken: jest.fn(),
authenticate: jest.fn(),
};
afterEach(() => jest.resetAllMocks());
it('findUser passes through the correct search params', async () => {
catalogApi.getEntities.mockResolvedValueOnce({ items: [{} as UserEntity] });
tokenIssuer.issueToken.mockResolvedValue('my-token');
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
const client = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
await client.findUser({ annotations: { key: 'value' } });
@@ -62,11 +62,7 @@ describe('CatalogIdentityClient', () => {
},
{ token: 'my-token' },
);
expect(tokenIssuer.issueToken).toHaveBeenCalledWith({
claims: {
sub: 'backstage.io/auth-backend',
},
});
expect(tokenManager.getToken).toHaveBeenCalledWith();
});
it('resolveCatalogMembership resolves membership', async () => {
@@ -114,35 +110,39 @@ describe('CatalogIdentityClient', () => {
},
];
catalogApi.getEntities.mockResolvedValueOnce({ items: mockUsers });
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
const client = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const claims = await client.resolveCatalogMembership({
entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'],
});
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'inigom',
},
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'imontoya',
},
{
kind: 'user',
'metadata.namespace': 'reality',
'metadata.name': 'mpatinkin',
},
],
});
expect(catalogApi.getEntities).toHaveBeenCalledWith(
{
filter: [
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'inigom',
},
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'imontoya',
},
{
kind: 'user',
'metadata.namespace': 'reality',
'metadata.name': 'mpatinkin',
},
],
},
{ token: 'my-token' },
);
expect(claims).toMatchObject([
'user:default/inigom',
@@ -24,7 +24,7 @@ import {
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
import { TokenIssuer } from '../../identity';
import { TokenManager } from '@backstage/backend-common';
type UserQuery = {
annotations: Record<string, string>;
@@ -40,11 +40,11 @@ type MemberClaimQuery = {
*/
export class CatalogIdentityClient {
private readonly catalogApi: CatalogApi;
private readonly tokenIssuer: TokenIssuer;
private readonly tokenManager: TokenManager;
constructor(options: { catalogApi: CatalogApi; tokenIssuer: TokenIssuer }) {
constructor(options: { catalogApi: CatalogApi; tokenManager: TokenManager }) {
this.catalogApi = options.catalogApi;
this.tokenIssuer = options.tokenIssuer;
this.tokenManager = options.tokenManager;
}
/**
@@ -61,9 +61,7 @@ export class CatalogIdentityClient {
}
// TODO(Rugvip): cache the token
const token = await this.tokenIssuer.issueToken({
claims: { sub: 'backstage.io/auth-backend' },
});
const { token } = await this.tokenManager.getToken();
const { items } = await this.catalogApi.getEntities({ filter }, { token });
if (items.length !== 1) {
@@ -106,8 +104,9 @@ export class CatalogIdentityClient {
'metadata.namespace': ref.namespace,
'metadata.name': ref.name,
}));
const { token } = await this.tokenManager.getToken();
const entities = await this.catalogApi
.getEntities({ filter })
.getEntities({ filter }, { token })
.then(r => r.items);
if (entityRefs.length !== entities.length) {
@@ -197,6 +197,7 @@ export const createAtlassianProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -208,7 +209,7 @@ export const createAtlassianProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> =
@@ -220,6 +220,7 @@ export const createAuth0Provider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -231,7 +232,7 @@ export const createAuth0Provider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
@@ -241,7 +241,7 @@ export type AwsAlbProviderOptions = {
export const createAwsAlbProvider = (
options?: AwsAlbProviderOptions,
): AuthProviderFactory => {
return ({ config, tokenIssuer, catalogApi, logger }) => {
return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => {
const region = config.getString('region');
const issuer = config.getOptionalString('iss');
@@ -253,7 +253,7 @@ export const createAwsAlbProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<AwsAlbResult> = options?.authHandler
@@ -273,6 +273,7 @@ export const createBitbucketProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -283,7 +284,7 @@ export const createBitbucketProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<BitbucketOAuthResult> =
@@ -102,7 +102,7 @@ export class GcpIapProvider implements AuthProviderRouteHandlers {
export function createGcpIapProvider(
options: GcpIapProviderOptions,
): AuthProviderFactory {
return ({ config, tokenIssuer, catalogApi, logger }) => {
return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => {
const audience = config.getString('audience');
const authHandler = options.authHandler ?? defaultAuthHandler;
@@ -111,7 +111,7 @@ export function createGcpIapProvider(
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
return new GcpIapProvider({
@@ -245,6 +245,7 @@ export const createGithubProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -270,7 +271,7 @@ export const createGithubProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<GithubOAuthResult> = options?.authHandler
@@ -227,6 +227,7 @@ export const createGitlabProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -239,7 +240,7 @@ export const createGitlabProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> =
@@ -259,6 +259,7 @@ export const createGoogleProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -269,7 +270,7 @@ export const createGoogleProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
@@ -267,6 +267,7 @@ export const createMicrosoftProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -281,7 +282,7 @@ export const createMicrosoftProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
@@ -182,12 +182,12 @@ export const createOauth2ProxyProvider =
<JWTPayload>(
options: Oauth2ProxyProviderOptions<JWTPayload>,
): AuthProviderFactory =>
({ catalogApi, logger, tokenIssuer }) => {
({ catalogApi, logger, tokenIssuer, tokenManager }) => {
const signInResolver = options.signIn.resolver;
const authHandler = options.authHandler;
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
return new Oauth2ProxyAuthProvider<JWTPayload>({
logger,
@@ -233,6 +233,7 @@ export const createOAuth2Provider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -249,7 +250,7 @@ export const createOAuth2Provider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
@@ -257,6 +257,7 @@ export const createOidcProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -272,7 +273,7 @@ export const createOidcProvider = (
const prompt = envConfig.getOptionalString('prompt');
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OidcAuthResult> = options?.authHandler
@@ -268,6 +268,7 @@ export const createOktaProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -286,7 +287,7 @@ export const createOktaProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = _options?.authHandler
@@ -219,6 +219,7 @@ export const createOneLoginProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) =>
@@ -230,7 +231,7 @@ export const createOneLoginProvider = (
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
@@ -187,12 +187,13 @@ export const createSamlProvider = (
globalConfig,
config,
tokenIssuer,
tokenManager,
catalogApi,
logger,
}) => {
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
tokenManager,
});
const authHandler: AuthHandler<SamlAuthResult> = options?.authHandler
+5 -1
View File
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import {
PluginEndpointDiscovery,
TokenManager,
} from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
@@ -124,6 +127,7 @@ export type AuthProviderFactoryOptions = {
globalConfig: AuthProviderConfig;
config: Config;
logger: Logger;
tokenManager: TokenManager;
tokenIssuer: TokenIssuer;
discovery: PluginEndpointDiscovery;
catalogApi: CatalogApi;
+11 -1
View File
@@ -25,6 +25,7 @@ import {
import {
PluginDatabaseManager,
PluginEndpointDiscovery,
TokenManager,
} from '@backstage/backend-common';
import { assertError, NotFoundError } from '@backstage/errors';
import { CatalogClient } from '@backstage/catalog-client';
@@ -41,13 +42,21 @@ export interface RouterOptions {
database: PluginDatabaseManager;
config: Config;
discovery: PluginEndpointDiscovery;
tokenManager: TokenManager;
providerFactories?: ProviderFactories;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger, config, discovery, database, providerFactories } = options;
const {
logger,
config,
discovery,
database,
tokenManager,
providerFactories,
} = options;
const router = Router();
const appUrl = config.getString('app.baseUrl');
@@ -105,6 +114,7 @@ export async function createRouter(
globalConfig: { baseUrl: authUrl, appUrl, isOriginAllowed },
config: providersConfig.getConfig(providerId),
logger,
tokenManager,
tokenIssuer,
discovery,
catalogApi,
@@ -17,6 +17,7 @@
import {
createServiceBuilder,
loadBackendConfig,
ServerTokenManager,
SingleHostDiscovery,
useHotMemoize,
} from '@backstage/backend-common';
@@ -58,6 +59,7 @@ export async function startStandaloneServer(
},
},
discovery,
tokenManager: ServerTokenManager.noop(),
});
const service = createServiceBuilder(module)