From 837751840161ab590e980977ed78f84d69695405 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Apr 2022 13:49:55 +0200 Subject: [PATCH] auth-backend: migrate microsoft provider to use resolver context Signed-off-by: Patrik Oldsberg --- .../src/providers/microsoft/provider.test.ts | 26 +----- .../src/providers/microsoft/provider.ts | 86 ++++++------------- 2 files changed, 30 insertions(+), 82 deletions(-) diff --git a/plugins/auth-backend/src/providers/microsoft/provider.test.ts b/plugins/auth-backend/src/providers/microsoft/provider.test.ts index 3a0fc5ad86..e9b4424a95 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.test.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.test.ts @@ -18,11 +18,10 @@ import { MicrosoftAuthProvider } from './provider'; import * as helpers from '../../lib/passport/PassportStrategyHelper'; import { OAuthResult } from '../../lib/oauth'; import { getVoidLogger } from '@backstage/backend-common'; -import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient } from '../../lib/catalog'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { AuthResolverContext } from '../types'; const mockFrameHandler = jest.spyOn( helpers, @@ -87,19 +86,10 @@ const setupHandlers = () => { describe('createMicrosoftProvider', () => { it('should auth', async () => { setupHandlers(); - const tokenIssuer = { - issueToken: jest.fn(), - listPublicKeys: jest.fn(), - }; - const catalogIdentityClient = { - findUser: jest.fn(), - }; const provider = new MicrosoftAuthProvider({ logger: getVoidLogger(), - catalogIdentityClient: - catalogIdentityClient as unknown as CatalogIdentityClient, - tokenIssuer: tokenIssuer as unknown as TokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: { email: fullProfile.emails![0]!.value, @@ -131,19 +121,9 @@ describe('createMicrosoftProvider', () => { it('should return the base64 encoded photo data of the profile', async () => { setupHandlers(); - const tokenIssuer = { - issueToken: jest.fn(), - listPublicKeys: jest.fn(), - }; - const catalogIdentityClient = { - findUser: jest.fn(), - }; - const provider = new MicrosoftAuthProvider({ logger: getVoidLogger(), - catalogIdentityClient: - catalogIdentityClient as unknown as CatalogIdentityClient, - tokenIssuer: tokenIssuer as unknown as TokenIssuer, + resolverContext: {} as AuthResolverContext, authHandler: async ({ fullProfile }) => ({ profile: { email: fullProfile.emails![0]!.value, diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 954f7742c4..053ab64547 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -17,8 +17,6 @@ import express from 'express'; import passport from 'passport'; import { Strategy as MicrosoftStrategy } from 'passport-microsoft'; -import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient, getEntityClaims } from '../../lib/catalog'; import { encodeState, OAuthAdapter, @@ -43,6 +41,7 @@ import { AuthHandler, RedirectInfo, SignInResolver, + AuthResolverContext, } from '../types'; import { Logger } from 'winston'; import fetch from 'node-fetch'; @@ -54,9 +53,8 @@ type PrivateInfo = { type Options = OAuthProviderOptions & { signInResolver?: SignInResolver; authHandler: AuthHandler; - tokenIssuer: TokenIssuer; - catalogIdentityClient: CatalogIdentityClient; logger: Logger; + resolverContext: AuthResolverContext; authorizationUrl?: string; tokenUrl?: string; }; @@ -65,16 +63,14 @@ export class MicrosoftAuthProvider implements OAuthHandlers { private readonly _strategy: MicrosoftStrategy; private readonly signInResolver?: SignInResolver; private readonly authHandler: AuthHandler; - private readonly tokenIssuer: TokenIssuer; - private readonly catalogIdentityClient: CatalogIdentityClient; private readonly logger: Logger; + private readonly resolverContext: AuthResolverContext; constructor(options: Options) { this.signInResolver = options.signInResolver; this.authHandler = options.authHandler; - this.tokenIssuer = options.tokenIssuer; this.logger = options.logger; - this.catalogIdentityClient = options.catalogIdentityClient; + this.resolverContext = options.resolverContext; this._strategy = new MicrosoftStrategy( { @@ -143,12 +139,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers { const photo = await this.getUserPhoto(result.accessToken); result.fullProfile.photos = photo ? [{ value: photo }] : undefined; - const context = { - logger: this.logger, - catalogIdentityClient: this.catalogIdentityClient, - tokenIssuer: this.tokenIssuer, - }; - const { profile } = await this.authHandler(result, context); + const { profile } = await this.authHandler(result, this.resolverContext); const response: OAuthResponse = { providerInfo: { @@ -166,35 +157,32 @@ export class MicrosoftAuthProvider implements OAuthHandlers { result, profile, }, - context, + this.resolverContext, ); } return response; } - private getUserPhoto(accessToken: string): Promise { - return new Promise(resolve => { - fetch('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', { - headers: { - Authorization: `Bearer ${accessToken}`, + private async getUserPhoto(accessToken: string): Promise { + try { + const res = await fetch( + 'https://graph.microsoft.com/v1.0/me/photos/48x48/$value', + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, - }) - .then(response => response.arrayBuffer()) - .then(arrayBuffer => { - const imageUrl = `data:image/jpeg;base64,${Buffer.from( - arrayBuffer, - ).toString('base64')}`; - resolve(imageUrl); - }) - .catch(error => { - this.logger.warn( - `Could not retrieve user profile photo from Microsoft Graph API: ${error}`, - ); - // User profile photo is optional, ignore errors and resolve undefined - resolve(undefined); - }); - }); + ); + const data = await res.buffer(); + + return `data:image/jpeg;base64,${data.toString('base64')}`; + } catch (error) { + this.logger.warn( + `Could not retrieve user profile photo from Microsoft Graph API: ${error}`, + ); + return undefined; + } } } @@ -208,16 +196,11 @@ export const microsoftEmailSignInResolver: SignInResolver = async ( throw new Error('Microsoft profile contained no email'); } - const entity = await ctx.catalogIdentityClient.findUser({ + return ctx.signInWithCatalogUser({ annotations: { 'microsoft.com/email': profile.email, }, }); - - const claims = getEntityClaims(entity); - const token = await ctx.tokenIssuer.issueToken({ claims }); - - return { id: entity.metadata.name, entity, token }; }; /** @@ -258,15 +241,7 @@ export const createMicrosoftProvider = (options?: { resolver: SignInResolver; }; }): AuthProviderFactory => { - return ({ - providerId, - globalConfig, - config, - tokenIssuer, - tokenManager, - catalogApi, - logger, - }) => + return ({ providerId, globalConfig, config, logger, resolverContext }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); @@ -279,11 +254,6 @@ export const createMicrosoftProvider = (options?: { const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; - const catalogIdentityClient = new CatalogIdentityClient({ - catalogApi, - tokenManager, - }); - const authHandler: AuthHandler = options?.authHandler ? options.authHandler : async ({ fullProfile, params }) => ({ @@ -298,15 +268,13 @@ export const createMicrosoftProvider = (options?: { tokenUrl, authHandler, signInResolver: options?.signIn?.resolver, - catalogIdentityClient, logger, - tokenIssuer, + resolverContext, }); return OAuthAdapter.fromConfig(globalConfig, provider, { disableRefresh: false, providerId, - tokenIssuer, callbackUrl, }); });