From 83efca5ebb5280e2e29d24a25764ff9ba055a85d Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Wed, 29 Jan 2020 17:21:51 +0200 Subject: [PATCH] Apply GitHub authentication plugin (#604) Add a plugin that authenticates vscode GitHub PR plugin launched in Che-Theia The plugin calls Che oAuth API service to get the GitHub token. Then it injects it to the user preferences. When the browser page is refreshed the vscode GitHub PR plugin fetches the token and cleans the token from the preferences file. --- che-theia-init-sources.yml | 1 + .../src/browser/che-github-main.ts | 33 +++++++-------- .../src/common/che-protocol.ts | 12 ++++-- .../src/node/che-api-service.ts | 15 +++++++ .../src/plugin/che-api.ts | 3 ++ .../src/plugin/che-github.ts | 4 ++ .../src/che-proposed.d.ts | 1 + plugins/github-auth-plugin/.gitignore | 3 ++ plugins/github-auth-plugin/README.md | 9 ++++ plugins/github-auth-plugin/package.json | 42 +++++++++++++++++++ .../src/github-auth-plugin.ts | 35 ++++++++++++++++ plugins/github-auth-plugin/tsconfig.json | 15 +++++++ plugins/github-auth-plugin/tsfmt.json | 18 ++++++++ yarn.lock | 6 +-- 14 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 plugins/github-auth-plugin/.gitignore create mode 100644 plugins/github-auth-plugin/README.md create mode 100644 plugins/github-auth-plugin/package.json create mode 100644 plugins/github-auth-plugin/src/github-auth-plugin.ts create mode 100644 plugins/github-auth-plugin/tsconfig.json create mode 100644 plugins/github-auth-plugin/tsfmt.json diff --git a/che-theia-init-sources.yml b/che-theia-init-sources.yml index 366a91b96..22f62d725 100644 --- a/che-theia-init-sources.yml +++ b/che-theia-init-sources.yml @@ -20,4 +20,5 @@ sources: - plugins/welcome-plugin - plugins/ssh-plugin - plugins/telemetry-plugin + - plugins/github-auth-plugin checkoutTo: master diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-github-main.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-github-main.ts index fc8f0149e..ba3f530d4 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-github-main.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-github-main.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { CheGithubMain } from '../common/che-protocol'; +import { CheApiService, CheGithubMain } from '../common/che-protocol'; import { interfaces } from 'inversify'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import axios, { AxiosInstance } from 'axios'; @@ -18,9 +18,11 @@ export class CheGithubMainImpl implements CheGithubMain { private axiosInstance: AxiosInstance = axios; private apiUrl: string; private token: string | undefined; + private readonly cheApiService: CheApiService; constructor(container: interfaces.Container) { this.envVariableServer = container.get(EnvVariablesServer); + this.cheApiService = container.get(CheApiService); this.envVariableServer.getValue('CHE_API').then(variable => { if (variable && variable.value) { this.apiUrl = variable.value; @@ -36,6 +38,15 @@ export class CheGithubMainImpl implements CheGithubMain { }); } + async $getToken(): Promise { + await this.fetchToken(); + if (this.token) { + return this.token; + } else { + throw new Error('Failed to get GitHub authentication token'); + } + } + private async fetchToken(): Promise { if (!this.token) { await this.updateToken(); @@ -49,17 +60,17 @@ export class CheGithubMainImpl implements CheGithubMain { } private async updateToken(): Promise { - this.token = await this.getToken(); + this.token = await this.cheApiService.getOAuthToken('github'); if (!this.token) { await this.authenticate(); - this.token = await this.getToken(); + this.token = await this.cheApiService.getOAuthToken('github'); } } private authenticate(): Promise { return new Promise(async (resolve, reject) => { const redirectUrl = window.location.href; - const url = `${this.apiUrl}/oauth/authenticate?oauth_provider=github&userId=${await this.getUserId()}` + + const url = `${this.apiUrl}/oauth/authenticate?oauth_provider=github&userId=${await this.cheApiService.getUserId()}` + `&scope=write:public_key&redirect_after_login=${redirectUrl}`; const popupWindow = window.open(url, 'popup'); const popup_close_handler = async () => { @@ -85,18 +96,4 @@ export class CheGithubMainImpl implements CheGithubMain { const popupCloseHandlerIntervalId = window.setInterval(popup_close_handler, 80); }); } - - private async getToken(): Promise { - try { - const result = await this.axiosInstance.get<{ token: string }>(`${this.apiUrl}/oauth/token?oauth_provider=github`); - return result.data.token; - } catch (e) { - return undefined; - } - } - - private async getUserId(): Promise { - const result = await this.axiosInstance.get<{ id: string }>(`${this.apiUrl}/user`); - return result.data.id; - } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts index 138ce92b3..47c2497b1 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts @@ -46,10 +46,6 @@ export interface CheFactoryMain { export interface CheDevfile { } -export interface CheGithub { - uploadPublicSshKey(publicKey: string): Promise; -} - export interface CheDevfileMain { $createWorkspace(devfilePath: string): Promise; } @@ -65,8 +61,14 @@ export interface CheSshMain { $deleteKey(service: string, name: string): Promise; } +export interface CheGithub { + uploadPublicSshKey(publicKey: string): Promise; + getToken(): Promise; +} + export interface CheGithubMain { $uploadPublicSshKey(publicKey: string): Promise; + $getToken(): Promise; } /** @@ -433,6 +435,7 @@ export interface CheApiService { getFactoryById(factoryId: string): Promise; + getUserId(): Promise; getUserPreferences(): Promise; getUserPreferences(filter: string | undefined): Promise; updateUserPreferences(update: Preferences): Promise; @@ -448,6 +451,7 @@ export interface CheApiService { getAllSshKey(service: string): Promise; submitTelemetryEvent(id: string, ownerId: string, ip: string, agent: string, resolution: string, properties: [string, string][]): Promise; submitTelemetryActivity(): Promise; + getOAuthToken(oAuthProvider: string): Promise; } export const CHE_TASK_SERVICE_PATH = '/che-task-service'; diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-api-service.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-api-service.ts index a7cf52373..a4a81538c 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/node/che-api-service.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-api-service.ts @@ -32,6 +32,12 @@ export class CheApiServiceImpl implements CheApiService { return process.env.CHE_API_INTERNAL; } + async getUserId(): Promise { + const cheApiClient = await this.getCheApiClient(); + const user = await cheApiClient.getCurrentUser(); + return user.id; + } + async getUserPreferences(filter?: string): Promise { const cheApiClient = await this.getCheApiClient(); return cheApiClient.getUserPreferences(filter); @@ -282,6 +288,15 @@ export class CheApiServiceImpl implements CheApiService { } } + async getOAuthToken(oAuthProvider: string): Promise { + const cheApiClient = await this.getCheApiClient(); + try { + return await cheApiClient.getOAuthToken(oAuthProvider); + } catch (e) { + return undefined; + } + } + private getWorkspaceTelemetryClient(): TelemetryClient | undefined { if (!this.telemetryClient) { diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts index 08cb2fe63..104ddce98 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-api.ts @@ -120,6 +120,9 @@ export function createAPIFactory(rpc: RPCProtocol): CheApiFactory { const github: typeof che.github = { uploadPublicSshKey(publicKey: string): Promise { return cheGithubImpl.uploadPublicSshKey(publicKey); + }, + getToken(): Promise { + return cheGithubImpl.getToken(); } }; diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-github.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-github.ts index b53bd8818..e5e872609 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-github.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-github.ts @@ -22,4 +22,8 @@ export class CheGithubImpl implements CheGithub { uploadPublicSshKey(publicKey: string): Promise { return this.githubMain.$uploadPublicSshKey(publicKey); } + + getToken(): Promise { + return this.githubMain.$getToken(); + } } diff --git a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts index 5bde37f75..678fff3f5 100644 --- a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts +++ b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts @@ -46,6 +46,7 @@ declare module '@eclipse-che/plugin' { export namespace github { export function uploadPublicSshKey(publicKey: string): Promise; + export function getToken(): Promise; } export namespace ssh { diff --git a/plugins/github-auth-plugin/.gitignore b/plugins/github-auth-plugin/.gitignore new file mode 100644 index 000000000..042d83637 --- /dev/null +++ b/plugins/github-auth-plugin/.gitignore @@ -0,0 +1,3 @@ +lib/ +node_modules/ +*.theia diff --git a/plugins/github-auth-plugin/README.md b/plugins/github-auth-plugin/README.md new file mode 100644 index 000000000..39f3b296e --- /dev/null +++ b/plugins/github-auth-plugin/README.md @@ -0,0 +1,9 @@ +# Theia - vscode Github pull-request plugin authenticator + +The plugin calls Che oAuth API service to get the GitHub token. +Then it injects it to the user preferences. When the browser page is refreshed, +the vscode GitHub PR plugin fetches the token and cleans the token from the preferences file. + +## License + +[EPL-2.0](http://www.eclipse.org/legal/epl-2.0) diff --git a/plugins/github-auth-plugin/package.json b/plugins/github-auth-plugin/package.json new file mode 100644 index 000000000..cb8c7bed8 --- /dev/null +++ b/plugins/github-auth-plugin/package.json @@ -0,0 +1,42 @@ +{ + "name": "@eclipse-che/github-auth-plugin", + "version": "0.0.1", + "publisher": "Eclipse Che", + "keywords": [ + "theia-plugin" + ], + "description": "Authenticates the vscode Github pull-request plugin", + "license": "EPL-2.0", + + "files": [ + "src" + ], + "activationEvents": [ + "*" + ], + "devDependencies": { + "@theia/plugin": "next", + "@theia/plugin-packager": "latest", + "@eclipse-che/plugin": "0.0.1", + "rimraf": "2.6.2", + "typescript-formatter": "7.2.2", + "typescript": "2.9.2", + "ts-loader": "^4.1.0" + }, + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "rimraf lib", + "format": "tsfmt -r --useTsfmt ../../configs/tsfmt.json", + "lint": "tslint -c ../../configs/tslint.json --project tsconfig.json", + "lint:fix": "tslint -c ../../configs/tslint.json --fix --project .", + "compile": "tsc", + "build": "yarn lint:fix && concurrently -n \"format,lint,compile\" -c \"red,green,blue\" \"yarn format\" \"yarn lint\" \"yarn compile\" && theia-plugin pack", + "watch": "tsc -w" + }, + "engines": { + "theiaPlugin": "next" + }, + "theiaPlugin": { + "backend": "lib/github-auth-plugin.js" + } +} diff --git a/plugins/github-auth-plugin/src/github-auth-plugin.ts b/plugins/github-auth-plugin/src/github-auth-plugin.ts new file mode 100644 index 000000000..67ba6ada8 --- /dev/null +++ b/plugins/github-auth-plugin/src/github-auth-plugin.ts @@ -0,0 +1,35 @@ +/********************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import * as theia from '@theia/plugin'; +import * as che from '@eclipse-che/plugin'; + +export function start(context: theia.PluginContext) { + if (theia.plugins.getPlugin('github.vscode-pull-request-github')) { + const command = { + id: 'github-plugin-authenticate', + label: 'GitHub authenticate' + }; + context.subscriptions.push(theia.commands.registerCommand(command, async () => { + const token = await che.github.getToken(); + const conf = await theia.workspace.getConfiguration(); + await conf.update('githubPullRequests.hosts', [{ + host: 'github.com', + token + }], theia.ConfigurationTarget.Global); + theia.window.showWarningMessage('GitHub token has been set to preferences. ' + + 'Refresh the page to reinitialise the vscode GitHub pull-request plugin with the token'); + })); + } +} + +export function stop() { + +} diff --git a/plugins/github-auth-plugin/tsconfig.json b/plugins/github-auth-plugin/tsconfig.json new file mode 100644 index 000000000..7a74a2a01 --- /dev/null +++ b/plugins/github-auth-plugin/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "skipLibCheck": true, + "lib": [ + "es6", + "webworker" + ], + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ] +} diff --git a/plugins/github-auth-plugin/tsfmt.json b/plugins/github-auth-plugin/tsfmt.json new file mode 100644 index 000000000..ad905f319 --- /dev/null +++ b/plugins/github-auth-plugin/tsfmt.json @@ -0,0 +1,18 @@ +{ + "baseIndentSize": 0, + "newLineCharacter": "\n", + "indentSize": 4, + "tabSize": 4, + "indentStyle": 4, + "convertTabsToSpaces": true, + "insertSpaceAfterCommaDelimiter": true, + "insertSpaceAfterSemicolonInForStatements": true, + "insertSpaceBeforeAndAfterBinaryOperators": true, + "insertSpaceAfterKeywordsInControlFlowStatements": true, + "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, + "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, + "placeOpenBraceOnNewLineForFunctions": false, + "placeOpenBraceOnNewLineForControlBlocks": false +} diff --git a/yarn.lock b/yarn.lock index 5f9062fb3..d65df45c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,9 +43,9 @@ "@eclipse-che/api" latest "@eclipse-che/workspace-client@latest": - version "0.0.1-1574171760" - resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1574171760.tgz#e4066030fe4cb35f21a82e173e37633d37c76520" - integrity sha512-lCnrrb9jKkxbO9QSmFERHvDxrN+8j1X/3RypESu7n7nrG53ubEqnnquTbU+vEgrMHyg1x0ctLRiUBQarM8dslg== + version "0.0.1-1579077578" + resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1579077578.tgz#0fc322b536eb46bf271effc3f00a0435038b5966" + integrity sha512-j3hPUaMcld6cw0wuqQd/EjjUAkkq7J5iri8MCJYJf2eSylLnjYRabNfnfVS4REgj3b02VXLx3kuDGmNPLacSEw== dependencies: "@eclipse-che/api" "^7.0.0-beta-4.0" axios "0.19.0"