Skip to content

Commit cc4f2d0

Browse files
Add Bitbucket Server auth provider
/werft no-preview
1 parent a33a4a0 commit cc4f2d0

7 files changed

+211
-0
lines changed

components/server/ee/src/auth/host-container-mapping.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export class HostContainerMappingEE extends HostContainerMapping {
2121
return (modules || []).concat([gitlabContainerModuleEE]);
2222
case "Bitbucket":
2323
return (modules || []).concat([bitbucketContainerModuleEE]);
24+
case "BitbucketServer":
25+
// FIXME
26+
return (modules || []).concat([bitbucketContainerModuleEE]);
2427
case "GitHub":
2528
return (modules || []).concat([gitHubContainerModuleEE]);
2629
default:

components/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"/dist"
2828
],
2929
"dependencies": {
30+
"@atlassian/bitbucket-server": "^0.0.6",
3031
"@gitbeaker/node": "^25.6.0",
3132
"@gitpod/content-service": "0.1.5",
3233
"@gitpod/gitpod-db": "0.1.5",

components/server/src/auth/host-container-mapping.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { githubContainerModule } from "../github/github-container-module";
99
import { gitlabContainerModule } from "../gitlab/gitlab-container-module";
1010
import { genericAuthContainerModule } from "./oauth-container-module";
1111
import { bitbucketContainerModule } from "../bitbucket/bitbucket-container-module";
12+
import { bitbucketServerContainerModule } from "../bitbucket-server/bitbucket-server-container-module";
1213

1314
@injectable()
1415
export class HostContainerMapping {
@@ -23,6 +24,8 @@ export class HostContainerMapping {
2324
return [genericAuthContainerModule];
2425
case "Bitbucket":
2526
return [bitbucketContainerModule];
27+
case "BitbucketServer":
28+
return [bitbucketServerContainerModule];
2629
default:
2730
return undefined;
2831
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { AuthProviderInfo } from "@gitpod/gitpod-protocol";
8+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
9+
import * as express from "express";
10+
import { injectable } from "inversify";
11+
import fetch from "node-fetch";
12+
import { AuthUserSetup } from "../auth/auth-provider";
13+
import { GenericAuthProvider } from "../auth/generic-auth-provider";
14+
import { BitbucketServerOAuthScopes } from "./bitbucket-server-oauth-scopes";
15+
import BitbucketServer = require("@atlassian/bitbucket-server")
16+
17+
@injectable()
18+
export class BitbucketServerAuthProvider extends GenericAuthProvider {
19+
20+
get info(): AuthProviderInfo {
21+
return {
22+
...this.defaultInfo(),
23+
scopes: BitbucketServerOAuthScopes.ALL,
24+
requirements: {
25+
default: BitbucketServerOAuthScopes.Requirements.DEFAULT,
26+
publicRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT,
27+
privateRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT,
28+
},
29+
}
30+
}
31+
32+
/**
33+
* Augmented OAuthConfig for Bitbucket
34+
*/
35+
protected get oauthConfig() {
36+
const oauth = this.params.oauth!;
37+
const scopeSeparator = " ";
38+
return <typeof oauth>{
39+
...oauth,
40+
authorizationUrl: oauth.authorizationUrl || `https://${this.params.host}/rest/oauth2/latest/authorize`,
41+
tokenUrl: oauth.tokenUrl || `https://${this.params.host}/rest/oauth2/latest/token`,
42+
settingsUrl: oauth.settingsUrl || `https://${this.params.host}/plugins/servlet/oauth/users/access-tokens/`,
43+
scope: BitbucketServerOAuthScopes.ALL.join(scopeSeparator),
44+
scopeSeparator
45+
};
46+
}
47+
48+
protected get tokenUsername(): string {
49+
return "x-token-auth";
50+
}
51+
52+
authorize(req: express.Request, res: express.Response, next: express.NextFunction, scope?: string[]): void {
53+
super.authorize(req, res, next, scope ? scope : BitbucketServerOAuthScopes.Requirements.DEFAULT);
54+
}
55+
56+
protected readAuthUserSetup = async (accessToken: string, _tokenResponse: object) => {
57+
try {
58+
59+
log.warn(`(${this.strategyName}) accessToken ${accessToken}`);
60+
61+
const fetchResult = await fetch(`https://${this.params.host}/plugins/servlet/applinks/whoami`, {
62+
headers: {
63+
"Authorization": `Bearer ${accessToken}`,
64+
}
65+
});
66+
if (!fetchResult.ok) {
67+
throw new Error(fetchResult.statusText);
68+
}
69+
const username = await fetchResult.text();
70+
if (!username) {
71+
throw new Error("username missing");
72+
}
73+
74+
log.warn(`(${this.strategyName}) username ${username}`);
75+
76+
const options = {
77+
baseUrl: `https://${this.params.host}`,
78+
};
79+
const client = new BitbucketServer(options);
80+
81+
client.authenticate({ type: "token", token: accessToken });
82+
const result = await client.api.getUser({ userSlug: username });
83+
84+
const user = result.data;
85+
// const headers = result.headers;
86+
87+
// const currentScopes = this.normalizeScopes((headers as any)["x-oauth-scopes"]
88+
// .split(",")
89+
// .map((s: string) => s.trim())
90+
// );
91+
92+
// TODO: check if user.active === true?
93+
94+
return <AuthUserSetup>{
95+
authUser: {
96+
authId: `${user.id!}`,
97+
authName: user.name!,
98+
primaryEmail: user.emailAddress!,
99+
name: user.displayName!,
100+
// avatarUrl: user.links!.avatar!.href // TODO
101+
},
102+
currentScopes: BitbucketServerOAuthScopes.ALL, // TODO
103+
}
104+
105+
} catch (error) {
106+
log.error(`(${this.strategyName}) Reading current user info failed`, error, { accessToken, error });
107+
throw error;
108+
}
109+
}
110+
111+
protected normalizeScopes(scopes: string[]) {
112+
const set = new Set(scopes);
113+
for (const item of set.values()) {
114+
if (!(BitbucketServerOAuthScopes.Requirements.DEFAULT.includes(item))) {
115+
set.delete(item);
116+
}
117+
}
118+
return Array.from(set).sort();
119+
}
120+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) 2020 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { ContainerModule } from "inversify";
8+
import { AuthProvider } from "../auth/auth-provider";
9+
import { BitbucketApiFactory } from '../bitbucket/bitbucket-api-factory';
10+
import { BitbucketContextParser } from "../bitbucket/bitbucket-context-parser";
11+
import { BitbucketFileProvider } from "../bitbucket/bitbucket-file-provider";
12+
import { BitbucketLanguagesProvider } from "../bitbucket/bitbucket-language-provider";
13+
import { BitbucketRepositoryProvider } from "../bitbucket/bitbucket-repository-provider";
14+
import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler";
15+
import { FileProvider, LanguagesProvider, RepositoryHost, RepositoryProvider } from "../repohost";
16+
import { IContextParser } from "../workspace/context-parser";
17+
import { BitbucketServerAuthProvider } from "./bitbucket-server-auth-provider";
18+
19+
export const bitbucketServerContainerModule = new ContainerModule((bind, _unbind, _isBound, _rebind) => {
20+
bind(RepositoryHost).toSelf().inSingletonScope();
21+
bind(BitbucketApiFactory).toSelf().inSingletonScope();
22+
bind(BitbucketFileProvider).toSelf().inSingletonScope();
23+
bind(FileProvider).toService(BitbucketFileProvider);
24+
bind(BitbucketContextParser).toSelf().inSingletonScope();
25+
bind(BitbucketLanguagesProvider).toSelf().inSingletonScope();
26+
bind(LanguagesProvider).toService(BitbucketLanguagesProvider);
27+
bind(IContextParser).toService(BitbucketContextParser);
28+
bind(BitbucketRepositoryProvider).toSelf().inSingletonScope();
29+
bind(RepositoryProvider).toService(BitbucketRepositoryProvider);
30+
bind(BitbucketServerAuthProvider).toSelf().inSingletonScope();
31+
bind(AuthProvider).to(BitbucketServerAuthProvider).inSingletonScope();
32+
bind(BitbucketTokenHelper).toSelf().inSingletonScope();
33+
// bind(BitbucketTokenValidator).toSelf().inSingletonScope(); // TODO
34+
// bind(IGitTokenValidator).toService(BitbucketTokenValidator);
35+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
// https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopesScopes
8+
9+
export namespace BitbucketServerOAuthScopes {
10+
/** View projects and repositories that are publicly accessible, including pulling code and cloning repositories. */
11+
export const PUBLIC_REPOS = "PUBLIC_REPOS";
12+
/** View projects and repositories the user account can view, including pulling code, cloning, and forking repositories. Create and comment on pull requests. */
13+
export const REPOSITORY_READ = "REPO_READ";
14+
/** Push over https, fork repo */
15+
export const REPOSITORY_WRITE = "REPO_WRITE";
16+
17+
export const ALL = [PUBLIC_REPOS, REPOSITORY_READ, REPOSITORY_WRITE];
18+
19+
export const Requirements = {
20+
/**
21+
* Minimal required permission.
22+
*/
23+
DEFAULT: ALL
24+
}
25+
}

yarn.lock

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
# yarn lockfile v1
33

44

5+
"@atlassian/bitbucket-server@^0.0.6":
6+
version "0.0.6"
7+
resolved "https://registry.yarnpkg.com/@atlassian/bitbucket-server/-/bitbucket-server-0.0.6.tgz#7a0678264083c851e50a66216e66021efe1638cf"
8+
integrity sha512-92EpKlSPw0ZZXiS4Qt+1DKuDxSIntL2j8Q0CWc8o/nUNOJAW/D9szIgcef5VPTbVslHeb2C3gBSMNnETdykdmQ==
9+
dependencies:
10+
before-after-hook "^1.1.0"
11+
btoa-lite "^1.0.0"
12+
debug "^3.1.0"
13+
is-plain-object "^2.0.4"
14+
node-fetch "^2.1.2"
15+
url-template "^2.0.8"
16+
517
618
version "7.10.4"
719
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@@ -4580,6 +4592,11 @@ bcrypt-pbkdf@^1.0.0:
45804592
dependencies:
45814593
tweetnacl "^0.14.3"
45824594

4595+
before-after-hook@^1.1.0:
4596+
version "1.4.0"
4597+
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d"
4598+
integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg==
4599+
45834600
before-after-hook@^2.1.0, before-after-hook@^2.2.0:
45844601
version "2.2.2"
45854602
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
@@ -12029,6 +12046,13 @@ node-emoji@^1.11.0:
1202912046
dependencies:
1203012047
lodash "^4.17.21"
1203112048

12049+
node-fetch@^2.1.2:
12050+
version "2.6.7"
12051+
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
12052+
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
12053+
dependencies:
12054+
whatwg-url "^5.0.0"
12055+
1203212056
node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5:
1203312057
version "2.6.6"
1203412058
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"

0 commit comments

Comments
 (0)