-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[server] add basic support for BitBucket Server
- Loading branch information
1 parent
a48e177
commit df307b3
Showing
12 changed files
with
601 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
components/server/src/bitbucket-server/bitbucket-api-factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/** | ||
* Copyright (c) 2020 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License-AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { User, Token } from "@gitpod/gitpod-protocol"; | ||
import { APIClient, Bitbucket } from "bitbucket"; | ||
import { inject, injectable } from "inversify"; | ||
import { AuthProviderParams } from "../auth/auth-provider"; | ||
import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; | ||
|
||
@injectable() | ||
export class BitbucketServerApiFactory { | ||
|
||
@inject(AuthProviderParams) protected readonly config: AuthProviderParams; | ||
@inject(BitbucketTokenHelper) protected readonly tokenHelper: BitbucketTokenHelper; | ||
|
||
/** | ||
* Returns a Bitbucket API client for the given user. | ||
* @param user The user the API client should be created for. | ||
*/ | ||
public async create(user: User): Promise<APIClient> { | ||
const token = await this.tokenHelper.getTokenWithScopes(user, []); | ||
return this.createBitbucket(this.baseUrl, token); | ||
} | ||
|
||
protected createBitbucket(baseUrl: string, token: Token): APIClient { | ||
return new Bitbucket({ | ||
baseUrl, | ||
auth: { | ||
token: token.value | ||
} | ||
}); | ||
} | ||
|
||
protected get baseUrl(): string { | ||
return `https://api.${this.config.host}/2.0`; | ||
} | ||
} | ||
|
||
@injectable() | ||
export class BasicAuthBitbucketServerApiFactory extends BitbucketServerApiFactory { | ||
protected createBitbucket(baseUrl: string, token: Token): APIClient { | ||
|
||
return new Bitbucket({ | ||
baseUrl, | ||
auth: { | ||
username: token.username!, | ||
password: token.value | ||
} | ||
}); | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
components/server/src/bitbucket-server/bitbucket-server-auth-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* Copyright (c) 2021 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License-AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { AuthProviderInfo } from "@gitpod/gitpod-protocol"; | ||
import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; | ||
import * as express from "express"; | ||
import { injectable } from "inversify"; | ||
import fetch from "node-fetch"; | ||
import { AuthUserSetup } from "../auth/auth-provider"; | ||
import { GenericAuthProvider } from "../auth/generic-auth-provider"; | ||
import { BitbucketServerOAuthScopes } from "./bitbucket-server-oauth-scopes"; | ||
import BitbucketServer = require("@atlassian/bitbucket-server") | ||
|
||
@injectable() | ||
export class BitbucketServerAuthProvider extends GenericAuthProvider { | ||
|
||
get info(): AuthProviderInfo { | ||
return { | ||
...this.defaultInfo(), | ||
scopes: BitbucketServerOAuthScopes.ALL, | ||
requirements: { | ||
default: BitbucketServerOAuthScopes.Requirements.DEFAULT, | ||
publicRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT, | ||
privateRepo: BitbucketServerOAuthScopes.Requirements.DEFAULT, | ||
}, | ||
} | ||
} | ||
|
||
/** | ||
* Augmented OAuthConfig for Bitbucket | ||
*/ | ||
protected get oauthConfig() { | ||
const oauth = this.params.oauth!; | ||
const scopeSeparator = " "; | ||
return <typeof oauth>{ | ||
...oauth, | ||
authorizationUrl: oauth.authorizationUrl || `https://${this.params.host}/rest/oauth2/latest/authorize`, | ||
tokenUrl: oauth.tokenUrl || `https://${this.params.host}/rest/oauth2/latest/token`, | ||
settingsUrl: oauth.settingsUrl || `https://${this.params.host}/plugins/servlet/oauth/users/access-tokens/`, | ||
scope: BitbucketServerOAuthScopes.ALL.join(scopeSeparator), | ||
scopeSeparator | ||
}; | ||
} | ||
|
||
protected get tokenUsername(): string { | ||
return "x-token-auth"; | ||
} | ||
|
||
authorize(req: express.Request, res: express.Response, next: express.NextFunction, scope?: string[]): void { | ||
super.authorize(req, res, next, scope ? scope : BitbucketServerOAuthScopes.Requirements.DEFAULT); | ||
} | ||
|
||
protected readAuthUserSetup = async (accessToken: string, _tokenResponse: object) => { | ||
try { | ||
|
||
log.warn(`(${this.strategyName}) accessToken ${accessToken}`); | ||
|
||
const fetchResult = await fetch(`https://${this.params.host}/plugins/servlet/applinks/whoami`, { | ||
headers: { | ||
"Authorization": `Bearer ${accessToken}`, | ||
} | ||
}); | ||
if (!fetchResult.ok) { | ||
throw new Error(fetchResult.statusText); | ||
} | ||
const username = await fetchResult.text(); | ||
if (!username) { | ||
throw new Error("username missing"); | ||
} | ||
|
||
log.warn(`(${this.strategyName}) username ${username}`); | ||
|
||
const options = { | ||
baseUrl: `https://${this.params.host}`, | ||
}; | ||
const client = new BitbucketServer(options); | ||
|
||
client.authenticate({ type: "token", token: accessToken }); | ||
const result = await client.api.getUser({ userSlug: username }); | ||
|
||
const user = result.data; | ||
// const headers = result.headers; | ||
|
||
// const currentScopes = this.normalizeScopes((headers as any)["x-oauth-scopes"] | ||
// .split(",") | ||
// .map((s: string) => s.trim()) | ||
// ); | ||
|
||
// TODO: check if user.active === true? | ||
|
||
return <AuthUserSetup>{ | ||
authUser: { | ||
authId: `${user.id!}`, | ||
authName: user.name!, | ||
primaryEmail: user.emailAddress!, | ||
name: user.displayName!, | ||
// avatarUrl: user.links!.avatar!.href // TODO | ||
}, | ||
currentScopes: BitbucketServerOAuthScopes.ALL, // TODO | ||
} | ||
|
||
} catch (error) { | ||
log.error(`(${this.strategyName}) Reading current user info failed`, error, { accessToken, error }); | ||
throw error; | ||
} | ||
} | ||
|
||
protected normalizeScopes(scopes: string[]) { | ||
const set = new Set(scopes); | ||
for (const item of set.values()) { | ||
if (!(BitbucketServerOAuthScopes.Requirements.DEFAULT.includes(item))) { | ||
set.delete(item); | ||
} | ||
} | ||
return Array.from(set).sort(); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
components/server/src/bitbucket-server/bitbucket-server-container-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* Copyright (c) 2020 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License-AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { ContainerModule } from "inversify"; | ||
import { AuthProvider } from "../auth/auth-provider"; | ||
import { BitbucketFileProvider } from "../bitbucket/bitbucket-file-provider"; | ||
import { BitbucketLanguagesProvider } from "../bitbucket/bitbucket-language-provider"; | ||
import { BitbucketRepositoryProvider } from "../bitbucket/bitbucket-repository-provider"; | ||
import { BitbucketTokenHelper } from "../bitbucket/bitbucket-token-handler"; | ||
import { FileProvider, LanguagesProvider, RepositoryHost, RepositoryProvider } from "../repohost"; | ||
import { IContextParser } from "../workspace/context-parser"; | ||
import { BitbucketServerApiFactory } from "./bitbucket-api-factory"; | ||
import { BitbucketServerAuthProvider } from "./bitbucket-server-auth-provider"; | ||
import { BitbucketServerContextParser } from "./bitbucket-server-context-parser"; | ||
|
||
export const bitbucketServerContainerModule = new ContainerModule((bind, _unbind, _isBound, _rebind) => { | ||
bind(RepositoryHost).toSelf().inSingletonScope(); | ||
bind(BitbucketServerApiFactory).toSelf().inSingletonScope(); | ||
bind(BitbucketFileProvider).toSelf().inSingletonScope(); | ||
bind(FileProvider).toService(BitbucketFileProvider); | ||
bind(BitbucketServerContextParser).toSelf().inSingletonScope(); | ||
bind(BitbucketLanguagesProvider).toSelf().inSingletonScope(); | ||
bind(LanguagesProvider).toService(BitbucketLanguagesProvider); | ||
bind(IContextParser).toService(BitbucketServerContextParser); | ||
bind(BitbucketRepositoryProvider).toSelf().inSingletonScope(); | ||
bind(RepositoryProvider).toService(BitbucketRepositoryProvider); | ||
bind(BitbucketServerAuthProvider).toSelf().inSingletonScope(); | ||
bind(AuthProvider).to(BitbucketServerAuthProvider).inSingletonScope(); | ||
bind(BitbucketTokenHelper).toSelf().inSingletonScope(); | ||
// bind(BitbucketTokenValidator).toSelf().inSingletonScope(); // TODO | ||
// bind(IGitTokenValidator).toService(BitbucketTokenValidator); | ||
}); |
Oops, something went wrong.