Skip to content

Commit 3644d93

Browse files
committed
[gitlab] Allow adding Git Provider using a relative URL
Signed-off-by: Alex Tugarev <[email protected]>
1 parent 70fe39a commit 3644d93

File tree

6 files changed

+68
-26
lines changed

6 files changed

+68
-26
lines changed

components/server/src/auth/auth-provider-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export class AuthProviderService {
144144
protected callbackUrl = (host: string) => {
145145
const pathname = `/auth/${host}/callback`;
146146
if (this.env.devBranch) {
147-
// for example: https://staging.gitpod-dev.com/auth/gitlab.com/callback
147+
// for example: https://staging.gitpod-dev.com/auth/mydomain.com/gitlab/callback
148148
return this.env.hostUrl.withoutDomainPrefix(1).with({ pathname }).toString();
149149
}
150150
return this.env.hostUrl.with({ pathname }).toString();

components/server/src/gitlab/gitlab-context-parser.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ export class GitlabContextParser extends AbstractContextParser implements IConte
245245
};
246246
}
247247
protected async fetchRepo(user: User, projectId: number | string): Promise<Repository> {
248+
// host might be a relative URL
249+
const host = this.host; // as per contract, cf. `canHandle(user, contextURL)`
250+
248251
const result = await this.gitlabApi.run<GitLab.Project>(user, async g => {
249252
return g.Projects.show(projectId);
250253
});
@@ -253,18 +256,24 @@ export class GitlabContextParser extends AbstractContextParser implements IConte
253256
}
254257
const { path, http_url_to_repo, namespace, forked_from_project, default_branch, visibility } = result;
255258
const repo = <Repository>{
256-
host: new URL(http_url_to_repo).hostname,
259+
host,
257260
name: path, // path is the last part of the URI (slug), e.g. "diaspora-project-site"
258261
owner: namespace.full_path,
259262
cloneUrl: http_url_to_repo,
260263
defaultBranch: default_branch,
261264
private: visibility === 'private'
262265
}
263266
if (forked_from_project) {
267+
268+
// host might be a relative URL, let's compute the prefix
269+
const url = new URL(forked_from_project.http_url_to_repo.split(forked_from_project.namespace.full_path)[0]);
270+
const relativePath = url.pathname.slice(1); // hint: pathname always starts with `/`
271+
const host = relativePath ? `${url.hostname}/${relativePath}` : url.hostname;
272+
264273
repo.fork = {
265274
parent: {
266275
name: forked_from_project.path,
267-
host: new URL(forked_from_project.http_url_to_repo).hostname,
276+
host,
268277
owner: forked_from_project.namespace.full_path,
269278
cloneUrl: forked_from_project.http_url_to_repo,
270279
defaultBranch: forked_from_project.default_branch

components/server/src/workspace/context-parser.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ export abstract class AbstractContextParser implements IContextParser {
2222

2323
@inject(AuthProviderParams) protected config: AuthProviderParams;
2424

25+
protected get host(): string {
26+
return this.config.host;
27+
}
28+
2529
public normalize(contextUrl: string): string | undefined {
2630
let url = contextUrl.trim();
27-
if (url.startsWith(`${this.config.host}/`)) {
31+
if (url.startsWith(`${this.host}/`)) {
2832
url = `https://${url}`;
2933
}
30-
if (url.startsWith(`https://${this.config.host}/`)) {
34+
if (url.startsWith(`https://${this.host}/`)) {
3135
return url;
3236
}
3337
return undefined;
@@ -41,14 +45,24 @@ export abstract class AbstractContextParser implements IContextParser {
4145
const url = new URL(contextUrl);
4246
const pathname = url.pathname.replace(/^\//, "").replace(/\/$/, ""); // pathname without leading and trailing slash
4347
const segments = pathname.split('/');
48+
49+
const host = this.host; // as per contract, cf. `canHandle(user, contextURL)`
50+
51+
const lenghtOfRelativePath = host.split("/").length - 1; // e.g. "123.123.123.123/gitlab" => length of 1
52+
if (lenghtOfRelativePath > 0) {
53+
// remove segments from the path to be consider further, which belong to the relative location of the host
54+
// cf. https://github.com/gitpod-io/gitpod/issues/2637
55+
segments.splice(0, lenghtOfRelativePath);
56+
}
57+
4458
var owner: string = segments[0];
4559
var repoName: string = segments[1];
4660
var moreSegmentsStart: number = 2;
4761
const endsWithRepoName = segments.length === moreSegmentsStart;
4862
const searchParams = url.searchParams;
4963
return {
50-
host: url.hostname,
51-
owner: owner,
64+
host,
65+
owner,
5266
repoName: this.parseRepoName(repoName, endsWithRepoName),
5367
moreSegments: endsWithRepoName ? [] : segments.slice(moreSegmentsStart),
5468
searchParams

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1520,7 +1520,11 @@ export class GitpodServerImpl<Client extends GitpodClient, Server extends Gitpod
15201520
}
15211521

15221522
// from https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address/106223#106223
1523-
protected validHostNameRegexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
1523+
// adapted to allow for hostnames
1524+
// from [foo.bar] pumped up to [foo.(foo.)bar]
1525+
// and also for a trailing path segments
1526+
// for example [foo.bar/gitlab]
1527+
protected validHostNameRegexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(\/([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$/;
15241528

15251529
async updateOwnAuthProvider({ entry }: GitpodServer.UpdateOwnAuthProviderParams): Promise<void> {
15261530
let userId: string;

components/theia/packages/gitpod-extension/src/browser/git-host-watcher.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,32 @@ export class GitHostWatcher implements FrontendApplicationContribution {
3232
this.gitRepository.onDidChangeRepository(() => this.update());
3333
}
3434

35-
protected gitHost: string | undefined;
35+
protected remoteUrl: string | undefined;
3636
protected async update() {
3737
const selectedRepo = this.gitRepository.selectedRepository;
38-
const gitHost = selectedRepo && await this.getGitHost(selectedRepo);
39-
if (this.gitHost === gitHost) {
40-
return;
38+
let remoteUrl = selectedRepo && await this.getRemoteUrl(selectedRepo);
39+
40+
// updates should be performed on changes only
41+
{
42+
if (this.remoteUrl === remoteUrl?.toString()) {
43+
return;
44+
}
45+
this.remoteUrl = remoteUrl?.toString();
4146
}
42-
this.gitHost = gitHost;
43-
const authProvider = gitHost && await this.getAuthProviderByHost(gitHost);
47+
48+
let gitHost = remoteUrl && remoteUrl.hostname;
49+
let authProvider = gitHost && await this.getAuthProviderByHost(gitHost);
50+
if (!authProvider && gitHost && remoteUrl) {
51+
// in case we cannot find the provider by hostname, let's try with simple path
52+
53+
const pathSegments = remoteUrl.pathname.split("/");
54+
const gitHostWithPath = `${remoteUrl.pathname}/${pathSegments[0] || pathSegments[1]}`;
55+
authProvider = await this.getAuthProviderByHost(gitHostWithPath);
56+
if (authProvider) {
57+
gitHost = gitHostWithPath;
58+
}
59+
}
60+
4461
const type = authProvider && authProvider.authProviderType;
4562
this.updateExtensions(type, gitHost);
4663
}
@@ -64,19 +81,11 @@ export class GitHostWatcher implements FrontendApplicationContribution {
6481
return this.authProviders;
6582
}
6683

67-
protected async getGitHost(repository: Repository): Promise<string | undefined> {
68-
const remoteUrl = await this.getRemoteUrl(repository);
69-
if (!remoteUrl) {
70-
return undefined;
71-
}
72-
const gitHost = new URL(remoteUrl).hostname;
73-
return gitHost;
74-
}
75-
76-
async getRemoteUrl(repository: Repository): Promise<string | undefined> {
84+
async getRemoteUrl(repository: Repository): Promise<URL | undefined> {
7785
try {
7886
const remoteUrlResult = await this.git.exec(repository, ["remote", "get-url", "origin"]);
79-
return remoteUrlResult.stdout.trim();
87+
const result = remoteUrlResult.stdout.trim();
88+
return new URL(result);
8089
} catch (e) {
8190
return undefined;
8291
}

components/theia/packages/gitpod-extension/src/browser/gitpod-git-token-provider.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ export class GitpodGitTokenProvider {
6161
async getGitToken(params: GetGitTokenParams): Promise<GetGitTokenResult> {
6262
const validationParams: TokenValidationParams = { ...params };
6363

64-
const token = await this.getTokenFromServer(params.host);
64+
let token = await this.getTokenFromServer(params.host);
65+
if (!token && params.repoURL) {
66+
const url = new URL(params.repoURL);
67+
const pathSegments = url.pathname.split("/");
68+
const hostAndPath = `${url.host}/${pathSegments[0] || pathSegments[1]}`;
69+
token = await this.getTokenFromServer(hostAndPath);
70+
}
6571
if (token) {
6672
const tokenUser = token.username || "oauth2";
6773
// if required scopes are missing, we can validate async

0 commit comments

Comments
 (0)