Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build

on:
workflow_dispatch: {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

push:
branches:
- '**'
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ Stay in the flow by using Atlassian for VSCode to start work on a JIRA issue, ra

[**Download now**](https://marketplace.visualstudio.com/items?itemName=Atlassian.atlascode&ssr=false#overview)

## [Devsphere specific changelog](/DEVSPHERE_CHANGELOG.md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

We're adding certain features & updates to this fork which may or may not be suitable to push upstream. Refer to the [Devsphere specific changelog](/DEVSPHERE_CHANGELOG.md) to get more context.

## Usage


### Getting Started

- Make sure you have VS Code version 1.77.0 or above
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 51 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
"onStartupFinished"
],
"extensionDependencies": [
"vscode.git",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"redhat.vscode-yaml"
"vscode.git"
],
"extensionKind": [
"workspace"
Expand Down Expand Up @@ -98,6 +97,16 @@
},
"category": "Atlassian"
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

"command": "atlascode.devsphere.review.initialise",
"title": "Initialise Devsphere Review Settings",
"category": "Atlassian"
},
{
"command": "atlascode.devsphere.custom.reset",
"title": "Reset Devsphere Custom Configuration",
"category": "Atlassian"
},
{
"command": "atlascode.jira.searchIssues",
"title": "Search Jira Issue Results",
Expand Down Expand Up @@ -402,6 +411,14 @@
"title": "Disable Help Explorer",
"category": "Atlassian"
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

"command": "atlascode.bitbucket.pullRequestsOverview.refresh",
"title": "Refresh",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
},
{
"command": "atlascode.showOnboardingFlow",
"title": "Show Onboarding Flow",
Expand Down Expand Up @@ -432,7 +449,12 @@
{
"id": "atlascode.views.bb.pullrequestsTreeView",
"name": "Bitbucket pull requests",
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled"
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

},
{
"id": "atlascode.views.bb.pullRequestsOverviewTreeView",
"name": "Bitbucket pull requests overview",
"when": "atlascode:bitbucketExplorerEnabled && config.atlascode.bitbucket.enabled && config.atlascode.bitbucket.explorer.pullRequestsOverview.enabled"
},
{
"id": "atlascode.views.bb.pipelinesTreeView",
Expand Down Expand Up @@ -579,6 +601,11 @@
{
"command": "atlascode.disableHelpExplorer",
"when": "view == atlascode.views.helpTreeView"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

{
"command": "atlascode.bitbucket.pullRequestsOverview.refresh",
"when": "view == atlascode.views.bb.pullRequestsOverviewTreeView",
"group": "navigation@1"
}
],
"view/item/context": [
Expand Down Expand Up @@ -825,7 +852,7 @@
"properties": {
"atlascode.outputLevel": {
"type": "string",
"default": "silent",
"default": "debug",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

"enum": [
"silent",
"errors",
Expand Down Expand Up @@ -1105,6 +1132,18 @@
"description": "Enables the Bitbucket Pull Request Explorer",
"scope": "window"
},
"atlascode.bitbucket.explorer.repositoryBasedPullRequestView.enabled": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

"type": "boolean",
"default": true,
"description": "Enable repository based pull requests tree view",
"scope": "window"
},
"atlascode.bitbucket.explorer.pullRequestsOverview.enabled": {
"type": "boolean",
"default": true,
"description": "Enable pull requests overview tree view",
"scope": "window"
},
"atlascode.bitbucket.explorer.nestFilesEnabled": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -1237,6 +1276,12 @@
"default": true,
"description": "Shows the help explorer treeview",
"scope": "window"
},
"atlascode.disableOnboarding": {
"type": "boolean",
"default": false,
"description": "Hide initial onboarding and help screens",
"scope": "window"
}
}
}
Expand Down Expand Up @@ -1284,6 +1329,7 @@
"@material-ui/styles": "^4.11.5",
"@segment/analytics-node": "^2.1.3",
"@vscode/webview-ui-toolkit": "^1.4.0",
"async-mutex": "^0.5.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

"awesome-debounce-promise": "^2.1.0",
"axios": "1.9.0",
"axios-curlirize": "^2.0.0",
Expand Down Expand Up @@ -1408,4 +1454,4 @@
"webpack-node-externals": "^3.0.0"
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
2 changes: 1 addition & 1 deletion src/atlclients/authInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface AuthInfo {

export interface OAuthInfo extends AuthInfo {
access: string;
refresh: string;
refresh?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

expirationDate?: number;
iat?: number;
recievedAt: number;
Expand Down
15 changes: 0 additions & 15 deletions src/atlclients/authStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,6 @@ describe('CredentialManager', () => {
site: mockJiraSite,
});
});

it("should not save to secret storage if auth info hasn't changed", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

// Setup existing auth info
const memStore = (credentialManager as any)._memStore;
const jiraStore = memStore.get(ProductJira.key);
jiraStore.set(mockJiraSite.credentialId, mockAuthInfo);

// Mock getAuthInfo to return the existing info
jest.spyOn(credentialManager, 'getAuthInfo').mockResolvedValue(mockAuthInfo);

await credentialManager.saveAuthInfo(mockJiraSite, mockAuthInfo);

expect(Container.context.secrets.store).not.toHaveBeenCalled();
expect(mockFireEvent).not.toHaveBeenCalled();
});
});

describe('refreshAccessToken', () => {
Expand Down
75 changes: 62 additions & 13 deletions src/atlclients/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Mutex } from 'async-mutex';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an older PR

import crypto from 'crypto';
import PQueue from 'p-queue';
import { Disposable, Event, EventEmitter, version, window } from 'vscode';
Expand Down Expand Up @@ -34,10 +35,15 @@ enum Priority {
Write,
}

function getKeyForSecrets(productKey: string, credentialId: string): string {
return `${productKey}-${credentialId}`;
}

export class CredentialManager implements Disposable {
private _memStore: Map<string, Map<string, AuthInfo>> = new Map<string, Map<string, AuthInfo>>();
private _queue = new PQueue({ concurrency: 1 });
private _refresher = new OAuthRefesher();
private mutex = new Mutex();

constructor(private _analyticsClient: AnalyticsClient) {
this._memStore.set(ProductJira.key, new Map<string, AuthInfo>());
Expand All @@ -59,21 +65,40 @@ export class CredentialManager implements Disposable {
* it's available, otherwise will return the value in the secretstorage.
*/
public async getAuthInfo(site: DetailedSiteInfo, allowCache = true): Promise<AuthInfo | undefined> {
return this.getAuthInfoForProductAndCredentialId(site, allowCache);
return this.mutex.runExclusive(() => {
return this.getAuthInfoForProductAndCredentialId(site, allowCache);
});
}

/**
* Saves the auth info to both the in-memory store and the secretstorage.
*/
public async saveAuthInfo(site: DetailedSiteInfo, info: AuthInfo): Promise<void> {
Logger.debug(`Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`);
return this.mutex.runExclusive(() => {
return this.saveAuthInfoForProductAndCredentialId(site, info);
});
}

/**
* Removes an auth item from both the in-memory store and the secretstorage.
*/
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> {
return this.mutex.runExclusive(() => {
return this.removeAuthInfoForProductAndCredentialId(site);
});
}

/**
* Saves the auth info to both the in-memory store and the secretstorage.
*/
public async saveAuthInfoForProductAndCredentialId(site: DetailedSiteInfo, info: AuthInfo): Promise<void> {
let productAuths = this._memStore.get(site.product.key);

if (!productAuths) {
productAuths = new Map<string, AuthInfo>();
}

const existingInfo = await this.getAuthInfo(site, false);
const existingInfo = await this.getAuthInfoForProductAndCredentialId(site, false);

this._memStore.set(site.product.key, productAuths.set(site.credentialId, info));

Expand All @@ -100,6 +125,29 @@ export class CredentialManager implements Disposable {
}
}

/**
* Saves the auth info to both the in-memory store and the secretstorage for Bitbucket API key login
*/
public async saveAuthInfoBBToken(site: DetailedSiteInfo, info: AuthInfo, id: number): Promise<void> {
Logger.debug(
`[id=${id}] Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId} using BB token`,
);
const productAuths = this._memStore.get(site.product.key);
Logger.debug(`[id=${id}] productAuths: ${productAuths}`);
if (!productAuths) {
this._memStore.set(site.product.key, new Map<string, AuthInfo>().set(site.credentialId, info));
Logger.debug(`[id=${id}] productAuths is empty so setting it to the new auth info in memstore`);
} else {
productAuths.set(site.credentialId, info);
this._memStore.set(site.product.key, productAuths);
Logger.debug(
`[id=${id}] productAuths is not empty so setting it to productAuth: ${productAuths}in memstore`,
);
}
Logger.debug(`[id=${id}] Calling saveAuthInfo for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`);
await this.saveAuthInfo(site, info);
}

private async getAuthInfoForProductAndCredentialId(
site: DetailedSiteInfo,
allowCache: boolean,
Expand Down Expand Up @@ -202,7 +250,10 @@ export class CredentialManager implements Disposable {
await this._queue.add(
async () => {
try {
await Container.context.secrets.store(`${productKey}-${credentialId}`, JSON.stringify(info));
await Container.context.secrets.store(
getKeyForSecrets(productKey, credentialId),
JSON.stringify(info),
);
} catch (e) {
Logger.error(e, `Error writing to secretstorage`);
}
Expand All @@ -217,7 +268,7 @@ export class CredentialManager implements Disposable {
let info: string | undefined = undefined;
await this._queue.add(
async () => {
info = await Container.context.secrets.get(`${productKey}-${credentialId}`);
info = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId));
},
{ priority: Priority.Read },
);
Expand All @@ -227,9 +278,9 @@ export class CredentialManager implements Disposable {
let wasKeyDeleted = false;
await this._queue.add(
async () => {
const storedInfo = await Container.context.secrets.get(`${productKey}-${credentialId}`);
const storedInfo = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId));
if (storedInfo) {
await Container.context.secrets.delete(`${productKey}-${credentialId}`);
await Container.context.secrets.delete(getKeyForSecrets(productKey, credentialId));
wasKeyDeleted = true;
}
},
Expand All @@ -244,7 +295,7 @@ export class CredentialManager implements Disposable {
if (keychain) {
wasKeyDeleted = await keychain.deletePassword(
keychainServiceNameV3,
`${productKey}-${credentialId}`,
getKeyForSecrets(productKey, credentialId),
);
}
},
Expand All @@ -256,9 +307,7 @@ export class CredentialManager implements Disposable {
private async getAuthInfoFromSecretStorage(
productKey: string,
credentialId: string,
serviceName?: string,
): Promise<AuthInfo | undefined> {
Logger.debug(`Retrieving secretstorage info for product: ${productKey} credentialID: ${credentialId}`);
let authInfo: string | undefined = undefined;
authInfo = await this.getSiteInformationFromSecretStorage(productKey, credentialId);
if (!authInfo) {
Expand Down Expand Up @@ -288,7 +337,7 @@ export class CredentialManager implements Disposable {
await this._queue.add(
async () => {
if (keychain) {
authInfo = await keychain.getPassword(svcName, `${productKey}-${credentialId}`);
authInfo = await keychain.getPassword(svcName, getKeyForSecrets(productKey, credentialId));
}
},
{ priority: Priority.Read },
Expand Down Expand Up @@ -320,7 +369,7 @@ export class CredentialManager implements Disposable {

const provider: OAuthProvider | undefined = oauthProviderForSite(site);
const newTokens = undefined;
if (provider && credentials) {
if (provider && credentials && credentials.refresh) {
const tokenResponse = await this._refresher.getNewTokens(provider, credentials.refresh);
if (tokenResponse.tokens) {
const newTokens = tokenResponse.tokens;
Expand All @@ -344,7 +393,7 @@ export class CredentialManager implements Disposable {
/**
* Removes an auth item from both the in-memory store and the secretstorage.
*/
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> {
public async removeAuthInfoForProductAndCredentialId(site: DetailedSiteInfo): Promise<boolean> {
const productAuths = this._memStore.get(site.product.key);
let wasKeyDeleted = false;
let wasMemDeleted = false;
Expand Down
Loading