From 42b897df3a1098a18d8b844bc853db8f973f3d8e Mon Sep 17 00:00:00 2001 From: Filip Swiatczak Date: Wed, 29 Sep 2021 12:52:10 +0100 Subject: [PATCH] cleanup + BitbucketAuth standardisation Signed-off-by: Filip Swiatczak --- docs/auth/bitbucket/provider.md | 30 +---- packages/core-app-api/api-report.md | 24 +--- .../auth/bitbucket/BitbucketAuth.test.ts | 34 ++++-- .../auth/bitbucket/BitbucketAuth.ts | 103 ++---------------- 4 files changed, 47 insertions(+), 144 deletions(-) diff --git a/docs/auth/bitbucket/provider.md b/docs/auth/bitbucket/provider.md index e827e0deab..ae09d5dbee 100644 --- a/docs/auth/bitbucket/provider.md +++ b/docs/auth/bitbucket/provider.md @@ -5,9 +5,9 @@ sidebar_label: Bitbucket description: Adding Bitbucket OAuth as an authentication provider in Backstage --- -The Backstage `core-api` package comes with a Bitbucket authentication provider -that can authenticate users using Bitbucket Cloud. This does **NOT** work with -Bitbucket Server. +The Backstage `core-plugin-api` package comes with a Bitbucket authentication +provider that can authenticate users using Bitbucket Cloud. This does **NOT** +work with Bitbucket Server. ## Create an OAuth Consumer in Bitbucket @@ -47,24 +47,6 @@ The Bitbucket provider is a structure with two configuration keys: ## Adding the provider to the Backstage frontend -Ensure identityProviders contains Bitbucket at -`packages/app/src/identityProviders.ts`: - -```yaml -import { - ... - bitbucketAuthApiRef, - ... -} from '@backstage/core-plugin-api'; - -export const providers = [ -... - { - id: 'bitbucket-auth-provider', - title: 'Bitbucket', - message: 'Sign In using Bitbucket Cloud', - apiRef: bitbucketAuthApiRef, - }, -... -]; -``` +To add the provider to the frontend, add the `bitbucketAuthApi` reference and +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index f3d1cfb20e..774be9dcdf 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -21,6 +21,7 @@ import { AuthRequestOptions } from '@backstage/core-plugin-api'; import { BackstageIdentity } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { ConfigReader } from '@backstage/config'; import { DiscoveryApi } from '@backstage/core-plugin-api'; @@ -272,9 +273,7 @@ export type BackstagePluginWithAnyOutput = Omit< // Warning: (ae-missing-release-tag) "BitbucketAuth" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class BitbucketAuth implements OAuthApi, SessionApi { - // Warning: (ae-forgotten-export) The symbol "SessionManager" needs to be exported by the entry point index.d.ts - constructor(sessionManager: SessionManager); +export class BitbucketAuth { // (undocumented) static create({ discoveryApi, @@ -282,23 +281,7 @@ export class BitbucketAuth implements OAuthApi, SessionApi { provider, oauthRequestApi, defaultScopes, - }: OAuthApiCreateOptions): BitbucketAuth; - // (undocumented) - getAccessToken(scope?: string, options?: AuthRequestOptions): Promise; - // (undocumented) - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; - // (undocumented) - static normalizeScope(scope?: string): Set; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; + }: OAuthApiCreateOptions): typeof bitbucketAuthApiRef.T; } // Warning: (ae-missing-release-tag) "BitbucketSession" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -404,6 +387,7 @@ export const FlatRoutes: (props: FlatRoutesProps) => JSX.Element | null; // // @public (undocumented) export class GithubAuth implements OAuthApi, SessionApi { + // Warning: (ae-forgotten-export) The symbol "SessionManager" needs to be exported by the entry point index.d.ts constructor(sessionManager: SessionManager); // (undocumented) static create({ diff --git a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts index 97de78f400..f4bd7cbe85 100644 --- a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.test.ts @@ -14,16 +14,34 @@ * limitations under the License. */ +import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; +import { UrlPatternDiscovery } from '../../DiscoveryApi'; import BitbucketAuth from './BitbucketAuth'; -describe('BitbucketAuth', () => { - it('should get access token', async () => { - const getSession = jest - .fn() - .mockResolvedValue({ providerInfo: { accessToken: 'access-token' } }); - const githubAuth = new BitbucketAuth({ getSession } as any); +const getSession = jest.fn(); - expect(await githubAuth.getAccessToken()).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); +jest.mock('../../../../lib/AuthSessionManager', () => ({ + ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), + RefreshingAuthSessionManager: class { + getSession = getSession; + }, +})); + +describe('BitbucketAuth', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it.each([ + ['team api write_repository', ['team', 'api', 'write_repository']], + ['read_repository sudo', ['read_repository', 'sudo']], + ])(`should normalize scopes correctly - %p`, (scope, scopes) => { + const gitlabAuth = BitbucketAuth.create({ + oauthRequestApi: new MockOAuthApi(), + discoveryApi: UrlPatternDiscovery.compile('http://example.com'), + }); + + gitlabAuth.getAccessToken(scope); + expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); }); }); diff --git a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts index 882f2cbeec..c88db2a2b0 100644 --- a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts @@ -15,23 +15,14 @@ */ import BitbucketIcon from '@material-ui/icons/FormatBold'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { BitbucketSession } from './types'; import { - OAuthApi, - SessionApi, - SessionState, - ProfileInfo, BackstageIdentity, - AuthRequestOptions, - Observable, + bitbucketAuthApiRef, + ProfileInfo, } from '@backstage/core-plugin-api'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { - AuthSessionStore, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; + import { OAuthApiCreateOptions } from '../types'; +import { OAuth2 } from '../oauth2'; export type BitbucketAuthResponse = { providerInfo: { @@ -49,94 +40,22 @@ const DEFAULT_PROVIDER = { icon: BitbucketIcon, }; -class BitbucketAuth implements OAuthApi, SessionApi { +class BitbucketAuth { static create({ discoveryApi, environment = 'development', provider = DEFAULT_PROVIDER, oauthRequestApi, defaultScopes = ['team'], - }: OAuthApiCreateOptions) { - const connector = new DefaultAuthConnector({ + }: OAuthApiCreateOptions): typeof bitbucketAuthApiRef.T { + return OAuth2.create({ discoveryApi, - environment, + oauthRequestApi, provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: BitbucketAuthResponse): BitbucketSession { - return { - ...res, - providerInfo: { - accessToken: res.providerInfo.accessToken, - scopes: BitbucketAuth.normalizeScope(res.providerInfo.scope), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, + environment, + defaultScopes, }); - - const sessionManager = new StaticAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: BitbucketSession) => session.providerInfo.scopes, - }); - - const authSessionStore = new AuthSessionStore({ - manager: sessionManager, - storageKey: `${provider.id}Session`, - sessionScopes: (session: BitbucketSession) => session.providerInfo.scopes, - }); - - return new BitbucketAuth(authSessionStore); - } - - constructor( - private readonly sessionManager: SessionManager, - ) {} - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken(scope?: string, options?: AuthRequestOptions) { - const session = await this.sessionManager.getSession({ - ...options, - scopes: BitbucketAuth.normalizeScope(scope), - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - static normalizeScope(scope?: string): Set { - if (!scope) { - return new Set(); - } - - const scopeList = Array.isArray(scope) - ? scope - : scope.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeList); } } + export default BitbucketAuth;