Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/askpass.ts
5243 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { window, InputBoxOptions, Uri, Disposable, workspace, QuickPickOptions, l10n, LogOutputChannel } from 'vscode';
7
import { IDisposable, EmptyDisposable, toDisposable, extractFilePathFromArgs } from './util';
8
import { IIPCHandler, IIPCServer } from './ipc/ipcServer';
9
import { CredentialsProvider, Credentials } from './api/git';
10
import { ITerminalEnvironmentProvider } from './terminal';
11
import { AskpassPaths } from './askpassManager';
12
13
export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider {
14
15
private env: { [key: string]: string };
16
private sshEnv: { [key: string]: string };
17
private disposable: IDisposable = EmptyDisposable;
18
private cache = new Map<string, Credentials>();
19
private credentialsProviders = new Set<CredentialsProvider>();
20
21
readonly featureDescription = 'git auth provider';
22
23
constructor(
24
private ipc: IIPCServer | undefined,
25
private readonly logger: LogOutputChannel,
26
askpassPaths: AskpassPaths
27
) {
28
if (ipc) {
29
this.disposable = ipc.registerHandler('askpass', this);
30
}
31
32
const askpassScript = this.ipc ? askpassPaths.askpass : askpassPaths.askpassEmpty;
33
const sshAskpassScript = this.ipc ? askpassPaths.sshAskpass : askpassPaths.sshAskpassEmpty;
34
35
this.env = {
36
// GIT_ASKPASS
37
GIT_ASKPASS: askpassScript,
38
// VSCODE_GIT_ASKPASS
39
VSCODE_GIT_ASKPASS_NODE: process.execPath,
40
VSCODE_GIT_ASKPASS_EXTRA_ARGS: '',
41
VSCODE_GIT_ASKPASS_MAIN: askpassPaths.askpassMain
42
};
43
44
this.sshEnv = {
45
// SSH_ASKPASS
46
SSH_ASKPASS: sshAskpassScript,
47
SSH_ASKPASS_REQUIRE: 'force'
48
};
49
}
50
51
async handle(payload: { askpassType: 'https' | 'ssh'; argv: string[] }): Promise<string> {
52
this.logger.trace(`[Askpass][handle] ${JSON.stringify(payload)}`);
53
54
const config = workspace.getConfiguration('git', null);
55
const enabled = config.get<boolean>('enabled');
56
57
if (!enabled) {
58
this.logger.trace(`[Askpass][handle] Git is disabled`);
59
return '';
60
}
61
62
return payload.askpassType === 'https'
63
? await this.handleAskpass(payload.argv)
64
: await this.handleSSHAskpass(payload.argv);
65
}
66
67
async handleAskpass(argv: string[]): Promise<string> {
68
// HTTPS (username | password)
69
// Username for 'https://github.com':
70
// Password for 'https://github.com':
71
const request = argv[2];
72
const host = argv[4].replace(/^["']+|["':]+$/g, '');
73
74
this.logger.trace(`[Askpass][handleAskpass] request: ${request}, host: ${host}`);
75
76
const uri = Uri.parse(host);
77
const authority = uri.authority.replace(/^.*@/, '');
78
const password = /password/i.test(request);
79
const cached = this.cache.get(authority);
80
81
if (cached && password) {
82
this.cache.delete(authority);
83
return cached.password;
84
}
85
86
if (!password) {
87
for (const credentialsProvider of this.credentialsProviders) {
88
try {
89
const credentials = await credentialsProvider.getCredentials(uri);
90
91
if (credentials) {
92
this.cache.set(authority, credentials);
93
setTimeout(() => this.cache.delete(authority), 60_000);
94
return credentials.username;
95
}
96
} catch { }
97
}
98
}
99
100
const options: InputBoxOptions = {
101
password,
102
placeHolder: request,
103
prompt: `Git: ${host}`,
104
ignoreFocusOut: true
105
};
106
107
return await window.showInputBox(options) || '';
108
}
109
110
async handleSSHAskpass(argv: string[]): Promise<string> {
111
// SSH (passphrase | authenticity)
112
const request = argv[3];
113
114
// passphrase
115
if (/passphrase/i.test(request)) {
116
// Commit signing - Enter passphrase:
117
// Commit signing - Enter passphrase for '/c/Users/<username>/.ssh/id_ed25519':
118
// Git operation - Enter passphrase for key '/c/Users/<username>/.ssh/id_ed25519':
119
let file: string | undefined = undefined;
120
if (argv[5] && !/key/i.test(argv[5])) {
121
file = extractFilePathFromArgs(argv, 5);
122
} else if (argv[6]) {
123
file = extractFilePathFromArgs(argv, 6);
124
}
125
126
this.logger.trace(`[Askpass][handleSSHAskpass] request: ${request}, file: ${file}`);
127
128
const options: InputBoxOptions = {
129
password: true,
130
placeHolder: l10n.t('Passphrase'),
131
prompt: file ? `SSH Key: ${file}` : undefined,
132
ignoreFocusOut: true
133
};
134
135
return await window.showInputBox(options) || '';
136
}
137
138
// authenticity
139
const host = argv[6].replace(/^["']+|["':]+$/g, '');
140
const fingerprint = argv[15];
141
142
this.logger.trace(`[Askpass][handleSSHAskpass] request: ${request}, host: ${host}, fingerprint: ${fingerprint}`);
143
144
const options: QuickPickOptions = {
145
canPickMany: false,
146
ignoreFocusOut: true,
147
placeHolder: l10n.t('Are you sure you want to continue connecting?'),
148
title: l10n.t('"{0}" has fingerprint "{1}"', host ?? '', fingerprint ?? '')
149
};
150
const items = [l10n.t('yes'), l10n.t('no')];
151
return await window.showQuickPick(items, options) ?? '';
152
}
153
154
getEnv(): { [key: string]: string } {
155
const config = workspace.getConfiguration('git');
156
return config.get<boolean>('useIntegratedAskPass') ? { ...this.env, ...this.sshEnv } : {};
157
}
158
159
getTerminalEnv(): { [key: string]: string } {
160
const config = workspace.getConfiguration('git');
161
return config.get<boolean>('useIntegratedAskPass') && config.get<boolean>('terminalAuthentication') ? this.env : {};
162
}
163
164
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
165
this.credentialsProviders.add(provider);
166
return toDisposable(() => this.credentialsProviders.delete(provider));
167
}
168
169
dispose(): void {
170
this.disposable.dispose();
171
}
172
}
173
174