From 5cb4d2eabce3be7ccd2ac356f46fd97bdace6d4e Mon Sep 17 00:00:00 2001 From: Daniel Deloff <44780793+rv-ddeloff@users.noreply.github.com> Date: Mon, 11 Oct 2021 16:45:50 -0400 Subject: [PATCH] updates scope configuration Signed-off-by: Daniel Deloff <44780793+rv-ddeloff@users.noreply.github.com> --- app-config.yaml | 5 + .../src/providers/atlassian/provider.test.ts | 114 +++++++++++++----- .../src/providers/atlassian/provider.ts | 6 +- .../src/providers/atlassian/strategy.ts | 6 +- 4 files changed, 99 insertions(+), 32 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index c24ac1701b..e1b648c472 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -364,6 +364,11 @@ auth: development: clientId: ${AUTH_BITBUCKET_CLIENT_ID} clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET} + atlassian: + development: + clientId: ${AUTH_ATLASSIAN_CLIENT_ID} + clientSecret: ${AUTH_ATLASSIAN_CLIENT_SECRET} + scopes: ${AUTH_ATLASSIAN_SCOPES} costInsights: engineerCost: 200000 products: diff --git a/plugins/auth-backend/src/providers/atlassian/provider.test.ts b/plugins/auth-backend/src/providers/atlassian/provider.test.ts index e561c2e037..58c7468246 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.test.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.test.ts @@ -14,12 +14,16 @@ * limitations under the License. */ -import { AtlassianAuthProvider } from './provider'; +import { + AtlassianAuthProvider, + atlassianDefaultSignInResolver, +} from './provider'; import * as helpers from '../../lib/passport/PassportStrategyHelper'; import { getVoidLogger } from '@backstage/backend-common'; import { TokenIssuer } from '../../identity'; import { CatalogIdentityClient } from '../../lib/catalog'; import { OAuthResult } from '../../lib/oauth'; +import { PassportProfile } from '../../lib/passport/types'; const mockFrameHandler = jest.spyOn( helpers, @@ -27,33 +31,34 @@ const mockFrameHandler = jest.spyOn( ) as unknown as jest.MockedFunction<() => Promise<{ result: OAuthResult }>>; describe('createAtlassianProvider', () => { + const tokenIssuer = { + issueToken: jest.fn(), + listPublicKeys: jest.fn(), + }; + const catalogIdentityClient = { + findUser: jest.fn(), + }; + + const provider = new AtlassianAuthProvider({ + logger: getVoidLogger(), + catalogIdentityClient: + catalogIdentityClient as unknown as CatalogIdentityClient, + tokenIssuer: tokenIssuer as unknown as TokenIssuer, + authHandler: async ({ fullProfile }) => ({ + profile: { + email: fullProfile.emails![0]!.value, + displayName: fullProfile.displayName, + picture: 'http://google.com/lols', + }, + }), + clientId: 'mock', + clientSecret: 'mock', + callbackUrl: 'mock', + scopes: [], + signInResolver: atlassianDefaultSignInResolver, + }); + it('should auth', async () => { - const tokenIssuer = { - issueToken: jest.fn(), - listPublicKeys: jest.fn(), - }; - const catalogIdentityClient = { - findUser: jest.fn(), - }; - - const provider = new AtlassianAuthProvider({ - logger: getVoidLogger(), - catalogIdentityClient: - catalogIdentityClient as unknown as CatalogIdentityClient, - tokenIssuer: tokenIssuer as unknown as TokenIssuer, - authHandler: async ({ fullProfile }) => ({ - profile: { - email: fullProfile.emails![0]!.value, - displayName: fullProfile.displayName, - picture: 'http://google.com/lols', - }, - }), - clientId: 'mock', - clientSecret: 'mock', - callbackUrl: 'mock', - scopes: [], - }); - mockFrameHandler.mockResolvedValueOnce({ result: { fullProfile: { @@ -79,6 +84,9 @@ describe('createAtlassianProvider', () => { }); const { response } = await provider.handler({} as any); expect(response).toEqual({ + backstageIdentity: { + id: 'conrad', + }, providerInfo: { accessToken: 'accessToken', expiresInSeconds: 123, @@ -93,4 +101,56 @@ describe('createAtlassianProvider', () => { }, }); }); + + it('should forward a new refresh token on refresh', async () => { + const mockRefreshToken = jest.spyOn( + helpers, + 'executeRefreshTokenStrategy', + ) as unknown as jest.MockedFunction<() => Promise<{}>>; + + mockRefreshToken.mockResolvedValueOnce({ + accessToken: 'a.b.c', + refreshToken: 'dont-forget-to-send-refresh', + params: { + id_token: 'my-id', + scope: 'read_user', + }, + }); + + const mockUserProfile = jest.spyOn( + helpers, + 'executeFetchUserProfileStrategy', + ) as unknown as jest.MockedFunction<() => Promise>; + + mockUserProfile.mockResolvedValueOnce({ + id: 'uid-my-id', + username: 'mockuser', + provider: 'atlassian', + displayName: 'Mocked User', + emails: [ + { + value: 'mockuser@gmail.com', + }, + ], + }); + + const response = await provider.refresh({} as any); + + expect(response).toEqual({ + backstageIdentity: { + id: 'mockuser', + }, + profile: { + displayName: 'Mocked User', + email: 'mockuser@gmail.com', + picture: 'http://google.com/lols', + }, + providerInfo: { + accessToken: 'a.b.c', + idToken: 'my-id', + refreshToken: 'dont-forget-to-send-refresh', + scope: 'read_user', + }, + }); + }); }); diff --git a/plugins/auth-backend/src/providers/atlassian/provider.ts b/plugins/auth-backend/src/providers/atlassian/provider.ts index a4ab5600e1..c7d1a9590c 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.ts @@ -145,7 +145,7 @@ export class AtlassianAuthProvider implements OAuthHandlers { providerInfo: { idToken: result.params.id_token, accessToken: result.accessToken, - refreshToken: result.refreshToken, // GitLab expires the old refresh token when used + refreshToken: result.refreshToken, scope: result.params.scope, expiresInSeconds: result.params.expires_in, }, @@ -229,7 +229,7 @@ export const createAtlassianProvider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const scopes = envConfig.getStringArray('scopes'); + const scopes = envConfig.getString('scopes'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ @@ -253,7 +253,7 @@ export const createAtlassianProvider = ( const provider = new AtlassianAuthProvider({ clientId, clientSecret, - scopes, + scopes: [scopes], callbackUrl, authHandler, signInResolver, diff --git a/plugins/auth-backend/src/providers/atlassian/strategy.ts b/plugins/auth-backend/src/providers/atlassian/strategy.ts index 630348b16d..d07b09c5f8 100644 --- a/plugins/auth-backend/src/providers/atlassian/strategy.ts +++ b/plugins/auth-backend/src/providers/atlassian/strategy.ts @@ -29,7 +29,7 @@ interface AtlassianStrategyOptions { clientID: string; clientSecret: string; callbackURL: string; - scope: string[]; + scope: string; } const defaultScopes = ['offline_access', 'read:me']; @@ -45,11 +45,13 @@ export default class AtlassianStrategy extends OAuth2Strategy { throw new TypeError('Atlassian requires a scope option'); } + const scopes = options.scope.split(' '); + const optionsWithURLs = { ...options, authorizationURL: `https://auth.atlassian.com/authorize`, tokenURL: `https://auth.atlassian.com/oauth/token`, - scope: Array.from(new Set([...defaultScopes, ...options.scope])), + scope: Array.from(new Set([...defaultScopes, ...scopes])), }; super(optionsWithURLs, verify);