mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)
This commit is contained in:
@@ -20,7 +20,16 @@ function main() {
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content));
|
||||
const githubAppId = process.env.GITHUB_APP_ID;
|
||||
const githubAppSecret = process.env.GITHUB_APP_SECRET;
|
||||
|
||||
if (githubAppId && githubAppSecret) {
|
||||
content.GITHUB_APP = { id: githubAppId, secret: githubAppSecret }
|
||||
}
|
||||
|
||||
if (Object.keys(content).length > 0) {
|
||||
fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content));
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri } from 'vscode';
|
||||
import { Uri, env } from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface ClientDetails {
|
||||
id?: string;
|
||||
@@ -19,6 +21,8 @@ export interface ClientConfig {
|
||||
VSO: ClientDetails;
|
||||
VSO_PPE: ClientDetails;
|
||||
VSO_DEV: ClientDetails;
|
||||
|
||||
GITHUB_APP: ClientDetails;
|
||||
}
|
||||
|
||||
export class Registrar {
|
||||
@@ -26,7 +30,8 @@ export class Registrar {
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
this._config = require('./config.json') as ClientConfig;
|
||||
const fileContents = fs.readFileSync(path.join(env.appRoot, 'extensions/github-authentication/src/common/config.json')).toString();
|
||||
this._config = JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
this._config = {
|
||||
OSS: {},
|
||||
@@ -35,10 +40,20 @@ export class Registrar {
|
||||
EXPLORATION: {},
|
||||
VSO: {},
|
||||
VSO_PPE: {},
|
||||
VSO_DEV: {}
|
||||
VSO_DEV: {},
|
||||
GITHUB_APP: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getGitHubAppDetails(): ClientDetails {
|
||||
if (!this._config.GITHUB_APP.id || !this._config.GITHUB_APP.secret) {
|
||||
throw new Error(`No GitHub App client configuration available`);
|
||||
}
|
||||
|
||||
return this._config.GITHUB_APP;
|
||||
}
|
||||
|
||||
getClientDetails(callbackUri: Uri): ClientDetails {
|
||||
let details: ClientDetails | undefined;
|
||||
switch (callbackUri.scheme) {
|
||||
|
||||
@@ -20,9 +20,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
displayName: 'GitHub',
|
||||
onDidChangeSessions: onDidChangeSessions.event,
|
||||
getSessions: () => Promise.resolve(loginService.sessions),
|
||||
login: async (scopes: string[]) => {
|
||||
login: async (scopeList: string[]) => {
|
||||
try {
|
||||
const session = await loginService.login(scopes.join(' '));
|
||||
const session = await loginService.login(scopeList.join(' '));
|
||||
Logger.info('Login success!');
|
||||
return session;
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { keychain } from './common/keychain';
|
||||
import { GitHubServer } from './githubServer';
|
||||
import Logger from './common/logger';
|
||||
|
||||
export const onDidChangeSessions = new vscode.EventEmitter<void>();
|
||||
export const onDidChangeSessions = new vscode.EventEmitter<vscode.AuthenticationSessionsChangeEvent>();
|
||||
|
||||
interface SessionData {
|
||||
id: string;
|
||||
@@ -29,14 +29,16 @@ export class GitHubAuthenticationProvider {
|
||||
private pollForChange() {
|
||||
setTimeout(async () => {
|
||||
const storedSessions = await this.readSessions();
|
||||
let didChange = false;
|
||||
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
|
||||
storedSessions.forEach(session => {
|
||||
const matchesExisting = this._sessions.some(s => s.id === session.id);
|
||||
// Another window added a session to the keychain, add it to our state as well
|
||||
if (!matchesExisting) {
|
||||
this._sessions.push(session);
|
||||
didChange = true;
|
||||
added.push(session.id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,12 +51,12 @@ export class GitHubAuthenticationProvider {
|
||||
this._sessions.splice(sessionIndex, 1);
|
||||
}
|
||||
|
||||
didChange = true;
|
||||
removed.push(session.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (didChange) {
|
||||
onDidChangeSessions.fire();
|
||||
if (added.length || removed.length) {
|
||||
onDidChangeSessions.fire({ added, removed, changed: [] });
|
||||
}
|
||||
|
||||
this.pollForChange();
|
||||
@@ -101,12 +103,22 @@ export class GitHubAuthenticationProvider {
|
||||
}
|
||||
|
||||
public async login(scopes: string): Promise<vscode.AuthenticationSession> {
|
||||
const token = await this._githubServer.login(scopes);
|
||||
const token = scopes === 'vso' ? await this.loginAndInstallApp(scopes) : await this._githubServer.login(scopes);
|
||||
const session = await this.tokenToSession(token, scopes.split(' '));
|
||||
await this.setToken(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
public async loginAndInstallApp(scopes: string): Promise<string> {
|
||||
const token = await this._githubServer.login(scopes);
|
||||
const hasUserInstallation = await this._githubServer.hasUserInstallation(token);
|
||||
if (hasUserInstallation) {
|
||||
return token;
|
||||
} else {
|
||||
return this._githubServer.installApp();
|
||||
}
|
||||
}
|
||||
|
||||
private async tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
|
||||
const userInfo = await this._githubServer.getUserInfo(token);
|
||||
return {
|
||||
|
||||
@@ -71,13 +71,58 @@ export class GitHubServer {
|
||||
Logger.info('Logging in...');
|
||||
const state = uuid();
|
||||
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
|
||||
const clientDetails = ClientRegistrar.getClientDetails(callbackUri);
|
||||
const clientDetails = scopes === 'vso' ? ClientRegistrar.getGitHubAppDetails() : ClientRegistrar.getClientDetails(callbackUri);
|
||||
const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`);
|
||||
|
||||
vscode.env.openExternal(uri);
|
||||
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
|
||||
}
|
||||
|
||||
public async hasUserInstallation(token: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Logger.info('Getting user installations...');
|
||||
const post = https.request({
|
||||
host: 'api.github.com',
|
||||
path: `/user/installations`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.machine-man-preview+json',
|
||||
Authorization: `token ${token}`,
|
||||
'User-Agent': 'Visual-Studio-Code'
|
||||
}
|
||||
}, result => {
|
||||
const buffer: Buffer[] = [];
|
||||
result.on('data', (chunk: Buffer) => {
|
||||
buffer.push(chunk);
|
||||
});
|
||||
result.on('end', () => {
|
||||
if (result.statusCode === 200) {
|
||||
const json = JSON.parse(Buffer.concat(buffer).toString());
|
||||
Logger.info('Got installation info!');
|
||||
const hasInstallation = json.installations.some((installation: { app_slug: string }) => installation.app_slug === 'microsoft-visual-studio-code');
|
||||
resolve(hasInstallation);
|
||||
} else {
|
||||
reject(new Error(result.statusMessage));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
post.end();
|
||||
post.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async installApp(): Promise<string> {
|
||||
const clientDetails = ClientRegistrar.getGitHubAppDetails();
|
||||
const state = uuid();
|
||||
const uri = vscode.Uri.parse(`https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${state}`);
|
||||
|
||||
vscode.env.openExternal(uri);
|
||||
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
|
||||
}
|
||||
|
||||
public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Logger.info('Getting account info...');
|
||||
|
||||
Reference in New Issue
Block a user