Skip to content
Snippets Groups Projects
Commit 3aa8c648 authored by Tomas Vik's avatar Tomas Vik :m:
Browse files

Merge branch 'tv/2025-02/use-authentication-api' into 'main'

feat: show all accounts in VS Code accounts view

Closes #1381

See merge request !2357



Merged-by: Tomas Vik's avatarTomas Vik <tvik@gitlab.com>
Approved-by: Olena Horal-Koretska's avatarOlena Horal-Koretska <ohoralkoretska@gitlab.com>
Reviewed-by: Olena Horal-Koretska's avatarOlena Horal-Koretska <ohoralkoretska@gitlab.com>
parents 66b6410b 65917164
No related branches found
No related tags found
1 merge request!2357feat: show all accounts in VS Code accounts view
Pipeline #1660788257 passed with warnings
import { createOAuthAccount } from '../../test_utils/entities'; import { createOAuthAccount, createTokenAccount } from '../../test_utils/entities';
import { AccountService } from '../account_service'; import { AccountService } from '../account_service';
import { createExtensionContext } from '../../../common/test_utils/entities'; import { createExtensionContext } from '../../../common/test_utils/entities';
import { GitLabAuthenticationProvider } from './gitlab_authentication_provider'; import { GitLabAuthenticationProvider } from './gitlab_authentication_provider';
jest.mock('../../commands/openers');
jest.mock('../../gitlab/gitlab_service');
jest.useFakeTimers();
describe('GitLabAuthenticationProvider', () => { describe('GitLabAuthenticationProvider', () => {
let accountService: AccountService; let accountService: AccountService;
const oauthAccount = createOAuthAccount('https://test.com', 1, 'oauth-1');
const patAccount = createTokenAccount('https://test.com', 2, 'pat-1');
beforeEach(async () => { beforeEach(async () => {
accountService = new AccountService(); accountService = new AccountService();
...@@ -17,13 +15,62 @@ describe('GitLabAuthenticationProvider', () => { ...@@ -17,13 +15,62 @@ describe('GitLabAuthenticationProvider', () => {
describe('getting existing session', () => { describe('getting existing session', () => {
it('gets a session if there is existing oauth account', async () => { it('gets a session if there is existing oauth account', async () => {
await accountService.addAccount(createOAuthAccount()); await accountService.addAccount(oauthAccount);
await accountService.addAccount(patAccount);
const provider = new GitLabAuthenticationProvider(accountService);
const [oauthSession, patSession] = await provider.getSessions(['api']);
expect(oauthSession.accessToken).toBe(oauthAccount.token);
expect(oauthSession.account.id).toBe(oauthAccount.id);
expect(oauthSession.account.label).toBe('user1 - https://test.com');
expect(patSession.accessToken).toBe(patAccount.token);
});
});
describe('notifying of changes', () => {
it('notifies about added accounts', async () => {
const listener = jest.fn();
const provider = new GitLabAuthenticationProvider(accountService); const provider = new GitLabAuthenticationProvider(accountService);
provider.onDidChangeSessions(listener);
const sessions = await provider.getSessions(['api']); await accountService.addAccount(oauthAccount);
expect(sessions).toHaveLength(1); expect(listener).toHaveBeenCalledWith({
expect(sessions[0].accessToken).toBe(createOAuthAccount().token); added: [expect.objectContaining({ accessToken: oauthAccount.token })],
removed: [],
});
}); });
it('notifies about removed accounts', async () => {
const listener = jest.fn();
const provider = new GitLabAuthenticationProvider(accountService);
await accountService.addAccount(oauthAccount);
provider.onDidChangeSessions(listener);
await accountService.removeAccount(oauthAccount.id);
expect(listener).toHaveBeenCalledWith({
removed: [expect.objectContaining({ accessToken: oauthAccount.token })],
added: [],
});
});
});
it('createSession throws an error', async () => {
const provider = new GitLabAuthenticationProvider(accountService);
await expect(provider.createSession()).rejects.toThrow(
/Creating `gitlab.com` sessions .* is not supported/,
);
});
it('removeSession deletes account', async () => {
await accountService.addAccount(oauthAccount);
const provider = new GitLabAuthenticationProvider(accountService);
await provider.removeSession(oauthAccount.id);
expect(accountService.getAllAccounts()).toHaveLength(0);
}); });
}); });
import vscode from 'vscode'; import vscode from 'vscode';
import { differenceBy } from 'lodash';
import { accountService, AccountService } from '../account_service'; import { accountService, AccountService } from '../account_service';
import { OAuthAccount } from '../../../common/platform/gitlab_account'; import { Account } from '../../../common/platform/gitlab_account';
import { sort } from '../../utils/sort';
const scopesString = (scopes: readonly string[]) => sort(scopes).join(); const convertAccountToSession = (account: Account): vscode.AuthenticationSession => ({
const convertAccountToAuthenticationSession = (
account: OAuthAccount,
): vscode.AuthenticationSession => ({
accessToken: account.token, accessToken: account.token,
id: account.id, id: account.id,
scopes: account.scopes, scopes: ['api'],
account: { account: {
id: account.id, id: account.id,
label: `${account.instanceUrl} (${account.username})`, label: `${account.username} - ${account.instanceUrl}`,
}, },
}); });
const makeDiff = (prev: Account[], next: Account[]) => {
const added = differenceBy(next, prev, a => a.id).map(convertAccountToSession);
const removed = differenceBy(prev, next, a => a.id).map(convertAccountToSession);
return { added, removed, changed: undefined };
};
export class GitLabAuthenticationProvider implements vscode.AuthenticationProvider { export class GitLabAuthenticationProvider implements vscode.AuthenticationProvider {
#eventEmitter = #eventEmitter =
new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>(); new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
#accountService: AccountService; #accountService: AccountService;
#prevAccounts: Account[];
constructor(as = accountService) { constructor(as = accountService) {
this.#accountService = as; this.#accountService = as;
this.#accountService.onDidChange(() => {
this.#eventEmitter.fire(makeDiff(this.#prevAccounts, this.#accountService.getAllAccounts()));
this.#prevAccounts = this.#accountService.getAllAccounts();
});
this.#prevAccounts = this.#accountService.getAllAccounts();
} }
onDidChangeSessions = this.#eventEmitter.event; onDidChangeSessions = this.#eventEmitter.event;
async getSessions(scopes?: readonly string[]): Promise<readonly vscode.AuthenticationSession[]> { async getSessions(scopes?: readonly string[]): Promise<readonly vscode.AuthenticationSession[]> {
return this.#accountService if (scopes && !scopes.includes('api')) return [];
.getAllAccounts() return this.#accountService.getAllAccounts().map(convertAccountToSession);
.filter((a): a is OAuthAccount => a.type === 'oauth')
.filter(a => !scopes || scopesString(a.scopes) === scopesString(scopes))
.map(convertAccountToAuthenticationSession);
} }
async createSession(/* scopes: readonly string[] */): Promise<vscode.AuthenticationSession> { async createSession(/* scopes: readonly string[] */): Promise<vscode.AuthenticationSession> {
// This would show to 3rd party extensions depending on our Auth provider. // This would show to 3rd party extensions depending on our Auth provider.
throw new Error( throw new Error(
'Creating `gitlab.com` sessions is no longer supported. It will be implemented again and better in https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/1381', 'Creating `gitlab.com` sessions from other extensions is not supported. Please create an issue if you are a 3rd party extension author and you would like to reuse GitLab account from the GitLab extension.',
); );
} }
...@@ -48,5 +54,3 @@ export class GitLabAuthenticationProvider implements vscode.AuthenticationProvid ...@@ -48,5 +54,3 @@ export class GitLabAuthenticationProvider implements vscode.AuthenticationProvid
await this.#accountService.removeAccount(sessionId); await this.#accountService.removeAccount(sessionId);
} }
} }
export const gitlabAuthenticationProvider = new GitLabAuthenticationProvider();
...@@ -338,7 +338,7 @@ export const activate = async (context: vscode.ExtensionContext) => { ...@@ -338,7 +338,7 @@ export const activate = async (context: vscode.ExtensionContext) => {
vscode.window.registerFileDecorationProvider(changeTypeDecorationProvider); vscode.window.registerFileDecorationProvider(changeTypeDecorationProvider);
vscode.authentication.registerAuthenticationProvider( vscode.authentication.registerAuthenticationProvider(
'gitlab', 'gitlab',
'GitLab.com Authentication', 'GitLab',
new GitLabAuthenticationProvider(), new GitLabAuthenticationProvider(),
); );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment