cleanup + BitbucketAuth standardisation

Signed-off-by: Filip Swiatczak <filip.swiatczak@gmail.com>
This commit is contained in:
Filip Swiatczak
2021-09-29 12:52:10 +01:00
parent d830ebabcb
commit 42b897df3a
4 changed files with 47 additions and 144 deletions
+6 -24
View File
@@ -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).
+4 -20
View File
@@ -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<BitbucketSession>);
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<string>;
// (undocumented)
getBackstageIdentity(
options?: AuthRequestOptions,
): Promise<BackstageIdentity | undefined>;
// (undocumented)
getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
// (undocumented)
static normalizeScope(scope?: string): Set<string>;
// (undocumented)
sessionState$(): Observable<SessionState>;
// (undocumented)
signIn(): Promise<void>;
// (undocumented)
signOut(): Promise<void>;
}: 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<GithubSession>);
// (undocumented)
static create({
@@ -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) });
});
});
@@ -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<BitbucketSession>({
manager: sessionManager,
storageKey: `${provider.id}Session`,
sessionScopes: (session: BitbucketSession) => session.providerInfo.scopes,
});
return new BitbucketAuth(authSessionStore);
}
constructor(
private readonly sessionManager: SessionManager<BitbucketSession>,
) {}
async signIn() {
await this.getAccessToken();
}
async signOut() {
await this.sessionManager.removeSession();
}
sessionState$(): Observable<SessionState> {
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<BackstageIdentity | undefined> {
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<string> {
if (!scope) {
return new Set();
}
const scopeList = Array.isArray(scope)
? scope
: scope.split(/[\s|,]/).filter(Boolean);
return new Set(scopeList);
}
}
export default BitbucketAuth;