From 59023b549cd12ec935947ecf3cd1a5de8ea8f25e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 12 Sep 2020 23:26:27 +0200 Subject: [PATCH 1/2] support auth in web playground --- .../code/browser/workbench/workbench-dev.html | 6 +- src/vs/code/browser/workbench/workbench.html | 3 + src/vs/code/browser/workbench/workbench.ts | 57 ++++++++++++------- .../parts/activitybar/activitybarActions.ts | 5 +- .../browser/authenticationService.ts | 2 +- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 0142bb7f53dfa..992fead7e940c 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -14,12 +14,12 @@ + + + - - - diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index d56e3fdd36a7c..b4f0ef89ce1c1 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -14,6 +14,9 @@ + + + diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 9b814729d318e..39bba698acbaa 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -17,6 +17,24 @@ import { isStandalone } from 'vs/base/browser/browser'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; +function doCreateUri(path: string, queryValues: Map): URI { + let query: string | undefined = undefined; + + if (queryValues) { + let index = 0; + queryValues.forEach((value, key) => { + if (!query) { + query = ''; + } + + const prefix = (index++ === 0) ? '' : '&'; + query += `${prefix}${key}=${encodeURIComponent(value)}`; + }); + } + + return URI.parse(window.location.href).with({ path, query }); +} + interface ICredential { service: string; account: string; @@ -68,7 +86,7 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { } async setPassword(service: string, account: string, password: string): Promise { - this.deletePassword(service, account); + this.doDeletePassword(service, account); this.credentials.push({ service, account, password }); @@ -76,6 +94,22 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { } async deletePassword(service: string, account: string): Promise { + const result = await this.doDeletePassword(service, account); + + if (result) { + const queryValues: Map = new Map(); + queryValues.set('logout', String(true)); + queryValues.set('service', service); + + await request({ + url: doCreateUri('/auth/logout', queryValues).toString(true) + }, CancellationToken.None); + } + + return result; + } + + async doDeletePassword(service: string, account: string): Promise { let found = false; this._credentials = this.credentials.filter(credential => { @@ -154,7 +188,7 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi // Start to poll on the callback being fired this.periodicFetchCallback(requestId, Date.now()); - return this.doCreateUri('/callback', queryValues); + return doCreateUri('/callback', queryValues); } private async periodicFetchCallback(requestId: string, startTime: number): Promise { @@ -164,7 +198,7 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); const result = await request({ - url: this.doCreateUri('/fetch-callback', queryValues).toString(true) + url: doCreateUri('/fetch-callback', queryValues).toString(true) }, CancellationToken.None); // Check for callback results @@ -185,23 +219,6 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi } } - private doCreateUri(path: string, queryValues: Map): URI { - let query: string | undefined = undefined; - - if (queryValues) { - let index = 0; - queryValues.forEach((value, key) => { - if (!query) { - query = ''; - } - - const prefix = (index++ === 0) ? '' : '&'; - query += `${prefix}${key}=${encodeURIComponent(value)}`; - }); - } - - return URI.parse(window.location.href).with({ path, query }); - } } class WorkspaceProvider implements IWorkspaceProvider { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 34184c32e0cde..6b079117b330f 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -200,7 +200,10 @@ export class AccountsActionViewItem extends ActivityActionViewItem { return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName); }); - const actions = hasEmbedderAccountSession ? [manageExtensionsAction] : [manageExtensionsAction, signOutAction]; + const actions = [manageExtensionsAction]; + if (!hasEmbedderAccountSession || authenticationSession?.canSignOut) { + actions.push(signOutAction); + } const menu = new SubmenuAction('activitybar.submenu', `${accountName} (${providerDisplayName})`, actions); menus.push(menu); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index db85f09a426ea..59f88d7cebff9 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -26,7 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } -export type AuthenticationSessionInfo = { readonly id: string, readonly accessToken: string, readonly providerId: string }; +export type AuthenticationSessionInfo = { readonly id: string, readonly accessToken: string, readonly providerId: string, readonly canSignOut?: boolean }; export async function getCurrentAuthenticationSessionInfo(environmentService: IWorkbenchEnvironmentService, productService: IProductService): Promise { if (environmentService.options?.credentialsProvider) { const authenticationSessionValue = await environmentService.options.credentialsProvider.getPassword(`${productService.urlProtocol}.login`, 'account'); From 52bdeff88030fc5ab9398c63d694464960e78af2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 13 Sep 2020 01:56:02 +0200 Subject: [PATCH 2/2] - send auth session - prepare credentials from auth session --- resources/web/code-web.js | 29 ++----- .../code/browser/workbench/workbench-dev.html | 4 +- src/vs/code/browser/workbench/workbench.html | 4 +- src/vs/code/browser/workbench/workbench.ts | 81 +++++++++++++------ 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/resources/web/code-web.js b/resources/web/code-web.js index e0742e5f83867..b3d1636bedd5f 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -369,32 +369,17 @@ async function handleRoot(req, res) { webConfigJSON._wrapWebWorkerExtHostInIframe = true; } - const credentials = []; - if (args['github-auth']) { - const sessionId = uuid.v4(); - credentials.push({ - service: 'code-oss.login', - account: 'account', - password: JSON.stringify({ - id: sessionId, - providerId: 'github', - accessToken: args['github-auth'] - }) - }, { - service: 'code-oss-github.login', - account: 'account', - password: JSON.stringify([{ - id: sessionId, - scopes: ['user:email'], - accessToken: args['github-auth'] - }]) - }); - } + const authSessionInfo = args['github-auth'] ? { + id: uuid.v4(), + providerId: 'github', + accessToken: args['github-auth'], + scopes: [['user:email'], ['repo']] + } : undefined; const data = (await readFile(WEB_MAIN)).toString() .replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => escapeAttribute(JSON.stringify(webConfigJSON))) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied .replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(dedupedBuiltInExtensions))) - .replace('{{WORKBENCH_CREDENTIALS}}', () => escapeAttribute(JSON.stringify(credentials))) + .replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '') .replace('{{WEBVIEW_ENDPOINT}}', ''); diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 992fead7e940c..381d7991ec1ab 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -14,8 +14,8 @@ - - + + diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index b4f0ef89ce1c1..06fcdd8d05597 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -14,8 +14,8 @@ - - + + diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 39bba698acbaa..d4f5eb1459b5a 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -16,6 +16,7 @@ import { isEqual } from 'vs/base/common/resources'; import { isStandalone } from 'vs/base/browser/browser'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; +import product from 'vs/platform/product/common/product'; function doCreateUri(path: string, queryValues: Map): URI { let query: string | undefined = undefined; @@ -45,6 +46,32 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; + private readonly authService: string | undefined; + + constructor() { + let authSessionInfo: { readonly id: string, readonly accessToken: string, readonly providerId: string, readonly canSignOut?: boolean, readonly scopes: string[][] } | undefined; + const authSessionElement = document.getElementById('vscode-workbench-auth-session'); + const authSessionElementAttribute = authSessionElement ? authSessionElement.getAttribute('data-settings') : undefined; + if (authSessionElementAttribute) { + try { + authSessionInfo = JSON.parse(authSessionElementAttribute); + } catch (error) { /* Invalid session is passed. Ignore. */ } + } + + if (authSessionInfo) { + // Settings Sync Entry + this.setPassword(`${product.urlProtocol}.login`, 'account', JSON.stringify(authSessionInfo)); + + // Auth extension Entry + this.authService = `${product.urlProtocol}-${authSessionInfo.providerId}.login`; + this.setPassword(this.authService, 'account', JSON.stringify(authSessionInfo.scopes.map(scopes => ({ + id: authSessionInfo!.id, + scopes, + accessToken: authSessionInfo!.accessToken + })))); + } + } + private _credentials: ICredential[] | undefined; private get credentials(): ICredential[] { if (!this._credentials) { @@ -91,25 +118,34 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { this.credentials.push({ service, account, password }); this.save(); + + try { + if (password && service === this.authService) { + const value = JSON.parse(password); + if (Array.isArray(value) && value.length === 0) { + await this.logout(service); + } + } + } catch (error) { + console.log(error); + } } async deletePassword(service: string, account: string): Promise { const result = await this.doDeletePassword(service, account); - if (result) { - const queryValues: Map = new Map(); - queryValues.set('logout', String(true)); - queryValues.set('service', service); - - await request({ - url: doCreateUri('/auth/logout', queryValues).toString(true) - }, CancellationToken.None); + if (result && service === this.authService) { + try { + await this.logout(service); + } catch (error) { + console.log(error); + } } return result; } - async doDeletePassword(service: string, account: string): Promise { + private async doDeletePassword(service: string, account: string): Promise { let found = false; this._credentials = this.credentials.filter(credential => { @@ -138,6 +174,16 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { .filter(credential => credential.service === service) .map(({ account, password }) => ({ account, password })); } + + private async logout(service: string): Promise { + const queryValues: Map = new Map(); + queryValues.set('logout', String(true)); + queryValues.set('service', service); + + await request({ + url: doCreateUri('/auth/logout', queryValues).toString(true) + }, CancellationToken.None); + } } class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvider { @@ -447,21 +493,6 @@ class WindowIndicator implements IWindowIndicator { window.location.href = `${window.location.origin}?${queryString}`; }; - // Credentials (with support of predefined ones via meta element) - const credentialsProvider = new LocalStorageCredentialsProvider(); - - const credentialsElement = document.getElementById('vscode-workbench-credentials'); - const credentialsElementAttribute = credentialsElement ? credentialsElement.getAttribute('data-settings') : undefined; - let credentials = undefined; - if (credentialsElementAttribute) { - try { - credentials = JSON.parse(credentialsElementAttribute); - for (const { service, account, password } of credentials) { - credentialsProvider.setPassword(service, account, password); - } - } catch (error) { /* Invalid credentials are passed. Ignore. */ } - } - // Finally create workbench create(document.body, { ...config, @@ -470,6 +501,6 @@ class WindowIndicator implements IWindowIndicator { productQualityChangeHandler, workspaceProvider, urlCallbackProvider: new PollingURLCallbackProvider(), - credentialsProvider + credentialsProvider: new LocalStorageCredentialsProvider() }); })();