Skip to content

Commit ae72514

Browse files
committed
[server] handle guessTokenScopes
1 parent a6e79bc commit ae72514

File tree

6 files changed

+75
-45
lines changed

6 files changed

+75
-45
lines changed

components/gitpod-protocol/src/protocol.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -891,13 +891,6 @@ export interface GuessGitTokenScopesParams {
891891
host: string;
892892
repoUrl: string;
893893
gitCommand: string;
894-
currentToken: GitToken;
895-
}
896-
897-
export interface GitToken {
898-
token: string;
899-
user: string;
900-
scopes: string[];
901894
}
902895

903896
export interface GuessedGitTokenScopes {

components/server/src/api/scm-service-api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ export class ScmServiceAPI implements ServiceImpl<typeof ScmServiceInterface> {
4343
request: GuessTokenScopesRequest,
4444
context: HandlerContext,
4545
): Promise<GuessTokenScopesResponse> {
46-
throw new ConnectError("unimplemented", Code.Unimplemented);
46+
const userId = ctxUserId();
47+
const { scopes, message } = await this.scmService.guessTokenScopes(userId, request);
48+
return new GuessTokenScopesResponse({
49+
scopes,
50+
message,
51+
});
4752
}
4853

4954
async searchRepositories(

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

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,44 +86,52 @@ export class AuthProviderService {
8686
return result.map(toPublic);
8787
}
8888

89+
async findAuthProviderDescription(user: User, host: string): Promise<AuthProviderInfo | undefined> {
90+
const provider =
91+
this.config.authProviderConfigs.find((p) => p.host.toLowerCase() === host?.toLowerCase()) ||
92+
(await this.getAllAuthProviderParams()).find((p) => p.host.toLowerCase() === host?.toLowerCase());
93+
return provider ? this.toInfo(provider) : undefined;
94+
}
95+
96+
// explicitly copy to avoid bleeding sensitive details
97+
private toInfo(ap: AuthProviderParams): AuthProviderInfo {
98+
return {
99+
authProviderId: ap.id,
100+
authProviderType: ap.type,
101+
ownerId: ap.ownerId,
102+
organizationId: ap.organizationId,
103+
verified: ap.verified,
104+
host: ap.host,
105+
icon: ap.icon,
106+
hiddenOnDashboard: ap.hiddenOnDashboard,
107+
disallowLogin: ap.disallowLogin,
108+
description: ap.description,
109+
scopes: getScopesOfProvider(ap),
110+
requirements: getRequiredScopes(ap),
111+
};
112+
}
113+
89114
async getAuthProviderDescriptions(user: User): Promise<AuthProviderInfo[]> {
90115
const { builtinAuthProvidersConfigured } = this.config;
91116

92117
const authProviders = [...(await this.getAllAuthProviderParams()), ...this.config.authProviderConfigs];
93118

94-
// explicitly copy to avoid bleeding sensitive details
95-
const toInfo = (ap: AuthProviderParams) =>
96-
<AuthProviderInfo>{
97-
authProviderId: ap.id,
98-
authProviderType: ap.type,
99-
ownerId: ap.ownerId,
100-
organizationId: ap.organizationId,
101-
verified: ap.verified,
102-
host: ap.host,
103-
icon: ap.icon,
104-
hiddenOnDashboard: ap.hiddenOnDashboard,
105-
disallowLogin: ap.disallowLogin,
106-
description: ap.description,
107-
scopes: getScopesOfProvider(ap),
108-
requirements: getRequiredScopes(ap),
109-
};
110-
111119
const result: AuthProviderInfo[] = [];
112120
for (const p of authProviders) {
113121
const identity = user.identities.find((i) => i.authProviderId === p.id);
114122
if (identity) {
115-
result.push(toInfo(p));
123+
result.push(this.toInfo(p));
116124
continue;
117125
}
118126
if (p.ownerId === user.id) {
119-
result.push(toInfo(p));
127+
result.push(this.toInfo(p));
120128
continue;
121129
}
122130
if (builtinAuthProvidersConfigured && !this.isBuiltIn(p)) {
123131
continue;
124132
}
125133
if (this.isNotHidden(p) && this.isVerified(p)) {
126-
result.push(toInfo(p));
134+
result.push(this.toInfo(p));
127135
}
128136
}
129137
return result;

components/server/src/scm/scm-service.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { Project, Token, User } from "@gitpod/gitpod-protocol";
1212
import { HostContextProvider } from "../auth/host-context-provider";
1313
import { RepoURL } from "../repohost";
1414
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
15+
import { AuthProviderService } from "../auth/auth-provider-service";
16+
import { UserService } from "../user/user-service";
17+
import { GitTokenScopeGuesser } from "../workspace/git-token-scope-guesser";
18+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
1519

1620
@injectable()
1721
export class ScmService {
@@ -20,6 +24,9 @@ export class ScmService {
2024
@inject(Authorizer) private readonly auth: Authorizer,
2125
@inject(TokenProvider) private readonly tokenProvider: TokenProvider,
2226
@inject(HostContextProvider) private readonly hostContextProvider: HostContextProvider,
27+
@inject(AuthProviderService) private readonly authProviderService: AuthProviderService,
28+
@inject(UserService) private readonly userService: UserService,
29+
@inject(GitTokenScopeGuesser) private readonly gitTokenScopeGuesser: GitTokenScopeGuesser,
2330
) {}
2431

2532
public async getToken(userId: string, query: { host: string }): Promise<Token> {
@@ -30,7 +37,7 @@ export class ScmService {
3037
return token;
3138
}
3239

33-
async installWebhookForPrebuilds(project: Project, installer: User) {
40+
public async installWebhookForPrebuilds(project: Project, installer: User) {
3441
// Install the prebuilds webhook if possible
3542
const { teamId, cloneUrl } = project;
3643
const parsedUrl = RepoURL.parseRepoUrl(project.cloneUrl);
@@ -42,4 +49,29 @@ export class ScmService {
4249
await repositoryService.installAutomatedPrebuilds(installer, cloneUrl);
4350
}
4451
}
52+
53+
/**
54+
* `guessTokenScopes` requires the same permissions as `getToken`.
55+
*/
56+
public async guessTokenScopes(
57+
userId: string,
58+
params: { host: string; repoUrl: string; gitCommand: string },
59+
): Promise<{ scopes?: string[]; message?: string }> {
60+
const { host, repoUrl, gitCommand } = params;
61+
62+
const user = await this.userService.findUserById(userId, userId);
63+
64+
const provider = await this.authProviderService.findAuthProviderDescription(user, host);
65+
if (!provider) {
66+
throw new ApplicationError(ErrorCodes.NOT_FOUND, `Auth provider not found.`);
67+
}
68+
69+
const { value: currentToken } = await this.getToken(userId, { host });
70+
return await this.gitTokenScopeGuesser.guessGitTokenScopes(provider, {
71+
host,
72+
repoUrl,
73+
gitCommand,
74+
currentToken,
75+
});
76+
}
4577
}

components/server/src/workspace/git-token-scope-guesser.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@ import { GitTokenValidator } from "./git-token-validator";
1111

1212
@injectable()
1313
export class GitTokenScopeGuesser {
14-
@inject(GitTokenValidator) tokenValidator: GitTokenValidator;
14+
constructor(@inject(GitTokenValidator) private readonly tokenValidator: GitTokenValidator) {}
1515

1616
async guessGitTokenScopes(
17-
authProvider: AuthProviderInfo | undefined,
18-
params: GuessGitTokenScopesParams,
17+
authProvider: AuthProviderInfo,
18+
params: GuessGitTokenScopesParams & { currentToken: string },
1919
): Promise<GuessedGitTokenScopes> {
20-
if (!authProvider) {
21-
return { message: "Unknown host" };
22-
}
2320
const { repoUrl, gitCommand, currentToken } = params;
2421

2522
const parsedRepoUrl = repoUrl && RepoURL.parseRepoUrl(repoUrl);
@@ -36,7 +33,7 @@ export class GitTokenScopeGuesser {
3633
owner,
3734
repo,
3835
repoKind,
39-
token: currentToken.token,
36+
token: currentToken,
4037
});
4138
const hasWriteAccess = validationResult && validationResult.writeAccessToRepo === true;
4239
if (hasWriteAccess) {

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ import { RepoURL } from "../repohost/repo-url";
110110
import { AuthorizationService } from "../user/authorization-service";
111111
import { UserAuthentication } from "../user/user-authentication";
112112
import { ContextParser } from "./context-parser-service";
113-
import { GitTokenScopeGuesser } from "./git-token-scope-guesser";
114113
import { isClusterMaintenanceError } from "./workspace-starter";
115114
import { HeadlessLogUrls } from "@gitpod/gitpod-protocol/lib/headless-workspace-log";
116115
import { ConfigProvider, InvalidGitpodYMLError } from "./config-provider";
@@ -218,8 +217,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
218217

219218
@inject(AuthProviderService) private readonly authProviderService: AuthProviderService,
220219

221-
@inject(GitTokenScopeGuesser) private readonly gitTokenScopeGuesser: GitTokenScopeGuesser,
222-
223220
@inject(ProjectsService) private readonly projectsService: ProjectsService,
224221

225222
@inject(IDEService) private readonly ideService: IDEService,
@@ -2363,13 +2360,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
23632360
}
23642361

23652362
async guessGitTokenScopes(ctx: TraceContext, params: GuessGitTokenScopesParams): Promise<GuessedGitTokenScopes> {
2366-
traceAPIParams(ctx, { params: censor(params, "currentToken") });
2363+
traceAPIParams(ctx, { params: censor(params as any, "currentToken") });
23672364

2368-
const authProviders = await this.getAuthProviders(ctx);
2369-
return this.gitTokenScopeGuesser.guessGitTokenScopes(
2370-
authProviders.find((p) => p.host == params.host),
2371-
params,
2372-
);
2365+
const user = await this.checkAndBlockUser("guessGitTokenScopes");
2366+
2367+
return await this.scmService.guessTokenScopes(user.id, { ...params });
23732368
}
23742369

23752370
async adminGetUser(ctx: TraceContext, userId: string): Promise<User> {

0 commit comments

Comments
 (0)