Skip to content

Commit d9d90a0

Browse files
author
Rachel Macfarlane
committed
Use GitHub app for VSO, closes microsoft#92675
1 parent d21cadb commit d9d90a0

10 files changed

Lines changed: 149 additions & 5 deletions

File tree

build/azure-pipelines/darwin/product-build-darwin.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ steps:
7777
yarn postinstall
7878
displayName: Run postinstall scripts
7979
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'))
80+
env:
81+
OSS_GITHUB_ID: "a5d3c261b032765a78de"
82+
OSS_GITHUB_SECRET: $(oss-github-client-secret)
83+
INSIDERS_GITHUB_ID: "31f02627809389d9f111"
84+
INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret)
85+
STABLE_GITHUB_ID: "baa8a44b5e861d918709"
86+
STABLE_GITHUB_SECRET: $(stable-github-client-secret)
87+
EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea"
88+
EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret)
89+
VSO_GITHUB_ID: "3d4be8f37a0325b5817d"
90+
VSO_GITHUB_SECRET: $(vso-github-client-secret)
91+
VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492"
92+
VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret)
93+
VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c"
94+
VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret)
95+
GITHUB_APP_ID: "Iv1.ae51e546bef24ff1"
96+
GITHUB_APP_SECRET: $(github-app-client-secret)
8097

8198
- script: |
8299
set -e

build/azure-pipelines/linux/product-build-linux-multiarch.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ steps:
8686
yarn postinstall
8787
displayName: Run postinstall scripts
8888
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'))
89+
env:
90+
OSS_GITHUB_ID: "a5d3c261b032765a78de"
91+
OSS_GITHUB_SECRET: $(oss-github-client-secret)
92+
INSIDERS_GITHUB_ID: "31f02627809389d9f111"
93+
INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret)
94+
STABLE_GITHUB_ID: "baa8a44b5e861d918709"
95+
STABLE_GITHUB_SECRET: $(stable-github-client-secret)
96+
EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea"
97+
EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret)
98+
VSO_GITHUB_ID: "3d4be8f37a0325b5817d"
99+
VSO_GITHUB_SECRET: $(vso-github-client-secret)
100+
VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492"
101+
VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret)
102+
VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c"
103+
VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret)
104+
GITHUB_APP_ID: "Iv1.ae51e546bef24ff1"
105+
GITHUB_APP_SECRET: $(github-app-client-secret)
89106

90107
- script: |
91108
set -e

build/azure-pipelines/linux/product-build-linux.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ steps:
7676
yarn postinstall
7777
displayName: Run postinstall scripts
7878
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'))
79+
env:
80+
OSS_GITHUB_ID: "a5d3c261b032765a78de"
81+
OSS_GITHUB_SECRET: $(oss-github-client-secret)
82+
INSIDERS_GITHUB_ID: "31f02627809389d9f111"
83+
INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret)
84+
STABLE_GITHUB_ID: "baa8a44b5e861d918709"
85+
STABLE_GITHUB_SECRET: $(stable-github-client-secret)
86+
EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea"
87+
EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret)
88+
VSO_GITHUB_ID: "3d4be8f37a0325b5817d"
89+
VSO_GITHUB_SECRET: $(vso-github-client-secret)
90+
VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492"
91+
VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret)
92+
VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c"
93+
VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret)
94+
GITHUB_APP_ID: "Iv1.ae51e546bef24ff1"
95+
GITHUB_APP_SECRET: $(github-app-client-secret)
7996

8097
- script: |
8198
set -e

build/azure-pipelines/product-compile.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ steps:
9292
VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret)
9393
VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c"
9494
VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret)
95+
GITHUB_APP_ID: "Iv1.ae51e546bef24ff1"
96+
GITHUB_APP_SECRET: $(github-app-client-secret)
9597

9698
# Mixin must run before optimize, because the CSS loader will
9799
# inline small SVGs

build/azure-pipelines/win32/product-build-win32.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ steps:
8686
exec { yarn postinstall }
8787
displayName: Run postinstall scripts
8888
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'))
89+
env:
90+
OSS_GITHUB_ID: "a5d3c261b032765a78de"
91+
OSS_GITHUB_SECRET: $(oss-github-client-secret)
92+
INSIDERS_GITHUB_ID: "31f02627809389d9f111"
93+
INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret)
94+
STABLE_GITHUB_ID: "baa8a44b5e861d918709"
95+
STABLE_GITHUB_SECRET: $(stable-github-client-secret)
96+
EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea"
97+
EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret)
98+
VSO_GITHUB_ID: "3d4be8f37a0325b5817d"
99+
VSO_GITHUB_SECRET: $(vso-github-client-secret)
100+
VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492"
101+
VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret)
102+
VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c"
103+
VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret)
104+
GITHUB_APP_ID: "Iv1.ae51e546bef24ff1"
105+
GITHUB_APP_SECRET: $(github-app-client-secret)
89106

90107
- powershell: |
91108
. build/azure-pipelines/win32/exec.ps1

extensions/github-authentication/build/postinstall.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ function main() {
2020
}
2121
}
2222

23+
const githubAppId = process.env.GITHUB_APP_ID;
24+
const githubAppSecret = process.env.GITHUB_APP_SECRET;
25+
26+
if (githubAppId && githubAppSecret) {
27+
content.GITHUB_APP = { id: githubAppId, secret: githubAppSecret }
28+
}
29+
2330
if (Object.keys(content).length > 0) {
2431
fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content));
2532
}

extensions/github-authentication/src/common/clientRegistrar.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface ClientConfig {
2121
VSO: ClientDetails;
2222
VSO_PPE: ClientDetails;
2323
VSO_DEV: ClientDetails;
24+
25+
GITHUB_APP: ClientDetails;
2426
}
2527

2628
export class Registrar {
@@ -38,10 +40,20 @@ export class Registrar {
3840
EXPLORATION: {},
3941
VSO: {},
4042
VSO_PPE: {},
41-
VSO_DEV: {}
43+
VSO_DEV: {},
44+
GITHUB_APP: {}
4245
};
4346
}
4447
}
48+
49+
getGitHubAppDetails(): ClientDetails {
50+
if (!this._config.GITHUB_APP.id || !this._config.GITHUB_APP.secret) {
51+
throw new Error(`No GitHub App client configuration available`);
52+
}
53+
54+
return this._config.GITHUB_APP;
55+
}
56+
4557
getClientDetails(callbackUri: Uri): ClientDetails {
4658
let details: ClientDetails | undefined;
4759
switch (callbackUri.scheme) {

extensions/github-authentication/src/extension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ export async function activate(context: vscode.ExtensionContext) {
2020
displayName: 'GitHub',
2121
onDidChangeSessions: onDidChangeSessions.event,
2222
getSessions: () => Promise.resolve(loginService.sessions),
23-
login: async (scopes: string[]) => {
23+
login: async (scopeList: string[]) => {
2424
try {
25-
const session = await loginService.login(scopes.join(' '));
25+
const session = await loginService.login(scopeList.join(' '));
2626
Logger.info('Login success!');
2727
return session;
2828
} catch (e) {

extensions/github-authentication/src/github.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,22 @@ export class GitHubAuthenticationProvider {
103103
}
104104

105105
public async login(scopes: string): Promise<vscode.AuthenticationSession> {
106-
const token = await this._githubServer.login(scopes);
106+
const token = scopes === 'vso' ? await this.loginAndInstallApp(scopes) : await this._githubServer.login(scopes);
107107
const session = await this.tokenToSession(token, scopes.split(' '));
108108
await this.setToken(session);
109109
return session;
110110
}
111111

112+
public async loginAndInstallApp(scopes: string): Promise<string> {
113+
const token = await this._githubServer.login(scopes);
114+
const hasUserInstallation = await this._githubServer.hasUserInstallation(token);
115+
if (hasUserInstallation) {
116+
return token;
117+
} else {
118+
return this._githubServer.installApp();
119+
}
120+
}
121+
112122
private async tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
113123
const userInfo = await this._githubServer.getUserInfo(token);
114124
return {

extensions/github-authentication/src/githubServer.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,58 @@ export class GitHubServer {
7171
Logger.info('Logging in...');
7272
const state = uuid();
7373
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
74-
const clientDetails = ClientRegistrar.getClientDetails(callbackUri);
74+
const clientDetails = scopes === 'vso' ? ClientRegistrar.getGitHubAppDetails() : ClientRegistrar.getClientDetails(callbackUri);
7575
const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`);
7676

7777
vscode.env.openExternal(uri);
7878
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
7979
}
8080

81+
public async hasUserInstallation(token: string): Promise<boolean> {
82+
return new Promise((resolve, reject) => {
83+
Logger.info('Getting user installations...');
84+
const post = https.request({
85+
host: 'api.github.com',
86+
path: `/user/installations`,
87+
method: 'GET',
88+
headers: {
89+
Accept: 'application/vnd.github.machine-man-preview+json',
90+
Authorization: `token ${token}`,
91+
'User-Agent': 'Visual-Studio-Code'
92+
}
93+
}, result => {
94+
const buffer: Buffer[] = [];
95+
result.on('data', (chunk: Buffer) => {
96+
buffer.push(chunk);
97+
});
98+
result.on('end', () => {
99+
if (result.statusCode === 200) {
100+
const json = JSON.parse(Buffer.concat(buffer).toString());
101+
Logger.info('Got installation info!');
102+
const hasInstallation = json.installations.some((installation: { app_slug: string }) => installation.app_slug === 'microsoft-visual-studio-code');
103+
resolve(hasInstallation);
104+
} else {
105+
reject(new Error(result.statusMessage));
106+
}
107+
});
108+
});
109+
110+
post.end();
111+
post.on('error', err => {
112+
reject(err);
113+
});
114+
});
115+
}
116+
117+
public async installApp(): Promise<string> {
118+
const clientDetails = ClientRegistrar.getGitHubAppDetails();
119+
const state = uuid();
120+
const uri = vscode.Uri.parse(`https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${state}`);
121+
122+
vscode.env.openExternal(uri);
123+
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
124+
}
125+
81126
public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> {
82127
return new Promise((resolve, reject) => {
83128
Logger.info('Getting account info...');

0 commit comments

Comments
 (0)