Skip to content

Commit

Permalink
Address comments in atlassian#28 (atlassian#43)
Browse files Browse the repository at this point in the history
* Patch 1 (atlassian#42)

* VSCODE-1050: PAT auth for hosted BitBucket(like for hosted Jira)

Like the API of Jira and Bitbucket, the API of Bitbucket supports Bearer
Auth using PAT instead of Basic Auth:
https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html

A number of other customers have expressed a need for this feature, not
just us (Cloud Software Group): We disallow the use of HTTP Basic Auth
for security reasons for the Confluence, Jira and Bitbucket APIs of our
self-hosted services. Instead, we require the use of Bearer Auth using a
PAT by the users.

A background information for this is that, that your security policy
requires 2FA or alternatively tokens, password authentication without a
2nd factor as implemented in HTTP Basic Authentication is prohibited by
company security policy, and therefore HTTP Basic Authentication is
prohibited.

Also, by security policy, passwords have to be changed frequently, so
even without that prohibition, password authentication would be very
cumbersome as frequent changes result in frequent breakdown of the
Atlascode login in VS Code.

Right now, the Atlascloud plugin supports authenticating to Jira using
PAT because of a requirement by a customer, but the same has not been
extended to Bitbucket, it only supports using username/password (Basic
Authentication).

curl -v --oauth2-bearer $PAT https://<self-hosted bitbucket server>/rest/api/1.0/users/userslug?avatarSize=48

Bearer Authorization is already available in atlascode, but not yet
enabled for Bitbucket, it just was requested for Jira and not for
Bitbucket:
https://bitbucket.org/atlassianlabs/atlascode/issues/237/allow-saml-sso-as-authentication-method

All that remains for Atlascode is to allow Bearer Authorization for Bitbucket as well.

This is what this PR does. It adds the same option to alternatively use
PAT auth for self-hosted Bitbucket like the Jira client of Atlascode
already implements.

Signed-off-by: Bernhard Kaindl <bernhard.kaindl@gmx.de>

* Update clientManager.ts

---------

Signed-off-by: Bernhard Kaindl <bernhard.kaindl@gmx.de>
Co-authored-by: Bernhard Kaindl <bernhard.kaindl@gmx.de>

* lint

* update .vscodeignore to not include test files

---------

Signed-off-by: Bernhard Kaindl <bernhard.kaindl@gmx.de>
Co-authored-by: Bernhard Kaindl <bernhard.kaindl@gmx.de>
  • Loading branch information
bwieger-atlassian-com and bkaindl authored Dec 17, 2024
1 parent 3b45b77 commit 8b417fc
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 14 deletions.
1 change: 1 addition & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ devhtml/**
.yalc/**
.storybook/**
jest.config.js
.test-extensions/**
19 changes: 15 additions & 4 deletions src/atlclients/clientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ export class ClientManager implements Disposable {
} else {
result = {
repositories: isBasicAuthInfo(info)
? new ServerRepositoriesApi(this.createBasicHTTPClient(site, info.username, info.password))
? new ServerRepositoriesApi(this.createHTTPClient(site, info))
: undefined!,
pullrequests: isBasicAuthInfo(info)
? new ServerPullRequestApi(this.createBasicHTTPClient(site, info.username, info.password))
? new ServerPullRequestApi(this.createHTTPClient(site, info))
: undefined!,
issues: undefined,
pipelines: undefined,
Expand Down Expand Up @@ -216,10 +216,21 @@ export class ClientManager implements Disposable {
);
}

private createBasicHTTPClient(site: DetailedSiteInfo, username: string, password: string): HTTPClient {
private createHTTPClient(site: DetailedSiteInfo, info: AuthInfo): HTTPClient {
let auth = '';
if (isBasicAuthInfo(info)) {
Logger.info('Using Username and Password Auth');
auth = `Basic ${Buffer.from(info.username + ':' + info.password).toString('base64')}`;
} else if (isPATAuthInfo(info)) {
Logger.info('Using PAT Auth');
auth = `Bearer ${info.token}`;
} else {
Logger.warn('Auth format not recognized');
}

return new HTTPClient(
site.baseApiUrl,
`Basic ${Buffer.from(username + ':' + password).toString('base64')}`,
auth,
getAgent(site),
async (response: AxiosResponse): Promise<Error> => {
let errString = 'Unknown error';
Expand Down
21 changes: 12 additions & 9 deletions src/atlclients/loginManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import { Logger } from '../logger';
import { OAuthDancer } from './oauthDancer';
import { SiteManager } from '../siteManager';

const slugRegex = /[\[\:\/\?#@\!\$&'\(\)\*\+,;\=%\\\[\]]/gi;

export class LoginManager {
private _dancer: OAuthDancer = OAuthDancer.Instance;
private _jiraAuthenticator: JiraAuthenticator;
Expand Down Expand Up @@ -156,24 +154,29 @@ export class LoginManager {
let apiUrl = '';
const protocol = site.protocol ? site.protocol : 'https:';
const contextPath = site.contextPath ? site.contextPath : '';
const transport = getAxiosInstance();
switch (site.product.key) {
case ProductJira.key:
siteDetailsUrl = `${protocol}//${site.host}${contextPath}/rest/api/2/myself`;
avatarUrl = `${protocol}//${site.host}${contextPath}/images/fav-jcore.png`;
apiUrl = `${protocol}//${site.host}${contextPath}/rest`;
break;
case ProductBitbucket.key:
const bbCredentials = credentials as BasicAuthInfo;
siteDetailsUrl = `${protocol}//${
site.host
}${contextPath}/rest/api/1.0/users/${bbCredentials.username.replace(slugRegex, '_')}?avatarSize=64`;
avatarUrl = '';
apiUrl = `${protocol}//${site.host}${contextPath}`;
// Needed when using a API key to login (credentials is PATAuthInfo):
const res = await transport(`${apiUrl}/rest/api/latest/build/capabilities`, {
method: 'GET',
headers: {
Authorization: authHeader,
},
...getAgent(site),
});
let ausername = res.headers['x-ausername'];
siteDetailsUrl = `${apiUrl}/rest/api/1.0/users/${ausername}`;
avatarUrl = `${apiUrl}/users/${ausername}/avatar.png?s=64`;
break;
}

const transport = getAxiosInstance();

const res = await transport(siteDetailsUrl, {
method: 'GET',
headers: {
Expand Down
2 changes: 1 addition & 1 deletion src/react/atlascode/config/auth/AuthDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export const AuthDialog: React.FunctionComponent<AuthDialogProps> = memo(
}}
>
<Tab label="Username and Password" />
{product.key === ProductJira.key && <Tab label="Personal Access Token" />}
<Tab label="Personal Access Token" />
</Tabs>
<TabPanel value={authTypeTabIndex} index={0}>
<Grid item>
Expand Down

0 comments on commit 8b417fc

Please sign in to comment.