diff --git a/.rebase/CHANGELOG.md b/.rebase/CHANGELOG.md index 4fa7e8630cf..60e9b3b178f 100644 --- a/.rebase/CHANGELOG.md +++ b/.rebase/CHANGELOG.md @@ -2,6 +2,12 @@ The file to keep a list of changed files which will potentionaly help to resolve rebase conflicts. +#### @vitaliy-guliy +https://github.com/che-incubator/che-code/pull/339 + +- code/src/vs/platform/product/common/product.ts +--- + #### @vitaliy-guliy https://github.com/che-incubator/che-code/pull/331 @@ -27,7 +33,6 @@ https://github.com/che-incubator/che-code/pull/337/commits/875893566c2acd0bb7031 - code/src/vs/base/common/network.ts --- - #### @benoitf https://github.com/che-incubator/che-code/commit/eed0a5213ba1b29b810d53f6365aaa2294165845#diff-2735bf66f14ee64b9ce6fdc30355a5e3085ae96a791cd01d65843a8dcef7c166 diff --git a/.rebase/replace/code/src/vs/platform/product/common/product.ts.json b/.rebase/replace/code/src/vs/platform/product/common/product.ts.json new file mode 100644 index 00000000000..c8979d271e5 --- /dev/null +++ b/.rebase/replace/code/src/vs/platform/product/common/product.ts.json @@ -0,0 +1,10 @@ +[ + { + "from": "import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';", + "by": "import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';\\\nimport { loadFromFileSystem } from 'vs/platform/product/common/che/product';" + }, + { + "from": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as IProductConfiguration;", + "by": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as IProductConfiguration;\\\n\\\tproduct = loadFromFileSystem();" + } +] diff --git a/code/extensions/che-api/src/impl/github-service-impl.ts b/code/extensions/che-api/src/impl/github-service-impl.ts index 62144edc386..e0ea3abf9e5 100644 --- a/code/extensions/che-api/src/impl/github-service-impl.ts +++ b/code/extensions/che-api/src/impl/github-service-impl.ts @@ -42,7 +42,7 @@ export class GithubServiceImpl implements GithubService { @inject(K8SServiceImpl) private readonly k8sService: K8SServiceImpl, @inject(Symbol.for('AxiosInstance')) private readonly axiosInstance: AxiosInstance ) { - this.iniitializeToken(); + this.initializeToken(); } private checkToken(): void { @@ -117,10 +117,10 @@ export class GithubServiceImpl implements GithubService { } // another token should be used by the Github Service after removing the Device Authentication token - this.iniitializeToken(); + this.initializeToken(); } - private async iniitializeToken(): Promise { + private async initializeToken(): Promise { this.logger.info('Github Service: extracting token...'); const deviceAuthToken = await this.getDeviceAuthToken(); diff --git a/code/src/vs/platform/product/common/che/product.ts b/code/src/vs/platform/product/common/che/product.ts new file mode 100644 index 00000000000..7483983d3ef --- /dev/null +++ b/code/src/vs/platform/product/common/che/product.ts @@ -0,0 +1,32 @@ +/********************************************************************** + * Copyright (c) 2024 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 + ***********************************************************************/ +/* eslint-disable header/header */ + +import { IProductConfiguration } from 'vs/base/common/product'; + +export function loadFromFileSystem(): IProductConfiguration { + const href = `./oss-dev/static/product.json`; + + try { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open("GET", href, false); + xmlhttp.send(); + + if (xmlhttp.status == 200 && xmlhttp.readyState == 4) { + return JSON.parse(xmlhttp.responseText); + } + + console.log(`Request failed with status: ${xmlhttp.status}, readyState: ${xmlhttp.readyState}`); + } catch (err) { + console.error(err); + } + + throw new Error(`Unable to load product.json from ${href}.`); +} diff --git a/code/src/vs/platform/product/common/product.ts b/code/src/vs/platform/product/common/product.ts index d6dd0fc33b5..403b287a43c 100644 --- a/code/src/vs/platform/product/common/product.ts +++ b/code/src/vs/platform/product/common/product.ts @@ -6,6 +6,7 @@ import { env } from 'vs/base/common/process'; import { IProductConfiguration } from 'vs/base/common/product'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; +import { loadFromFileSystem } from 'vs/platform/product/common/che/product'; /** * @deprecated You MUST use `IProductService` if possible. @@ -54,6 +55,7 @@ else { // Built time configuration (do NOT modify) product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; + product = loadFromFileSystem(); // Running out of sources if (Object.keys(product).length === 0) { diff --git a/launcher/src/main.ts b/launcher/src/main.ts index 3fc22d248b8..90b33d4972b 100644 --- a/launcher/src/main.ts +++ b/launcher/src/main.ts @@ -12,6 +12,7 @@ import { CodeWorkspace } from './code-workspace'; import { DevWorkspaceId } from './devworkspace-id'; import { NodeExtraCertificate } from './node-extra-certificate'; import { OpenVSIXRegistry } from './openvsix-registry'; +import { TrustedExtensions } from './trusted-extensions'; import { VSCodeLauncher } from './vscode-launcher'; import { WebviewResources } from './webview-resources'; @@ -27,6 +28,7 @@ export class Main { await new OpenVSIXRegistry().configure(); await new WebviewResources().configure(); await new NodeExtraCertificate().configure(); + await new TrustedExtensions().configure(); const workspaceFile = await new CodeWorkspace().generate(); diff --git a/launcher/src/product-json.ts b/launcher/src/product-json.ts index bbd1a46fe2a..c02191be04a 100644 --- a/launcher/src/product-json.ts +++ b/launcher/src/product-json.ts @@ -12,6 +12,10 @@ import * as fs from './fs-extra'; const PRODUCT_JSON = 'product.json'; +export interface Record { + [key: string]: string[]; +} + export class ProductJSON { private json: any; @@ -89,4 +93,12 @@ export class ProductJSON { gallery.itemUrl = url; } + + getTrustedExtensionAuthAccess(): string[] | Record | undefined { + return this.json.trustedExtensionAuthAccess; + } + + setTrustedExtensionAuthAccess(trustedExtensionAuthAccess: string[] | Record | undefined) { + this.json.trustedExtensionAuthAccess = trustedExtensionAuthAccess; + } } diff --git a/launcher/src/trusted-extensions.ts b/launcher/src/trusted-extensions.ts new file mode 100644 index 00000000000..fa87b15329b --- /dev/null +++ b/launcher/src/trusted-extensions.ts @@ -0,0 +1,72 @@ +/********************************************************************** + * Copyright (c) 2024 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 { env } from 'process'; +import { ProductJSON } from './product-json'; + +export class TrustedExtensions { + async configure(): Promise { + console.log('# Configuring Trusted Extensions...'); + + if (!env.VSCODE_TRUSTED_EXTENSIONS) { + console.log(' > env.VSCODE_TRUSTED_EXTENSIONS is not defined, skip this step'); + return; + } + + console.log(` > env.VSCODE_TRUSTED_EXTENSIONS is set to [${env.VSCODE_TRUSTED_EXTENSIONS}]`); + + try { + const extensions: string[] = []; + + for (const extension of env.VSCODE_TRUSTED_EXTENSIONS.split(',')) { + if (extension) { + if (extension.match(/^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-.]*$/)) { + extensions.push(extension); + console.log(` > add ${extension}`); + } else { + console.log(` > failure to add [${extension}] because of wrong identifier`); + } + } + } + + if (!extensions.length) { + console.log( + ' > ERROR: The variable provided most likely has wrong format. It should specify one or more extensions separated by comma.' + ); + return; + } + + const productJSON = await new ProductJSON().load(); + let productJSONChanged = false; + + let access = productJSON.getTrustedExtensionAuthAccess(); + if (access === undefined) { + productJSON.setTrustedExtensionAuthAccess([...extensions]); + productJSONChanged = true; + } else if (Array.isArray(access)) { + for (const extension of extensions) { + if (!access.includes(extension)) { + access.push(extension); + productJSONChanged = true; + } + } + } else { + console.log(' > Unexpected type of trustedExtensionAuthAccess in product.json. Skip this step'); + return; + } + + if (productJSONChanged) { + await productJSON.save(); + } + } catch (err) { + console.error(`${err.message} Failure to configure trusted extensions in product.json.`); + } + } +} diff --git a/launcher/tests/main.spec.ts b/launcher/tests/main.spec.ts index 0a59a921f1b..42307c02b99 100644 --- a/launcher/tests/main.spec.ts +++ b/launcher/tests/main.spec.ts @@ -38,6 +38,13 @@ jest.mock('../src/node-extra-certificate', () => ({ }, })); +const configureTustedExtensions = jest.fn(); +jest.mock('../src/trusted-extensions', () => ({ + TrustedExtensions: function () { + return { configure: configureTustedExtensions }; + }, +})); + const generateCodeWorkspace = jest.fn(); jest.mock('../src/code-workspace', () => ({ CodeWorkspace: function () { @@ -60,6 +67,7 @@ describe('Test main flow:', () => { expect(configureOpenVSIXRegistryMock).toBeCalled(); expect(configureWebviewResourcesMock).toBeCalled(); expect(configureNodeExtraCertificate).toBeCalled(); + expect(configureTustedExtensions).toBeCalled(); expect(generateCodeWorkspace).toBeCalled(); diff --git a/launcher/tests/trusted-extensions.spec.ts b/launcher/tests/trusted-extensions.spec.ts new file mode 100644 index 00000000000..b9d15fda3a0 --- /dev/null +++ b/launcher/tests/trusted-extensions.spec.ts @@ -0,0 +1,247 @@ +/********************************************************************** + * Copyright (c) 2024 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 { env } from 'process'; +import * as fs from '../src/fs-extra'; + +import { TrustedExtensions } from '../src/trusted-extensions'; + +const PRODUCT_JSON_SIMPLE = `{ + "version": "1.0.0" +}`; + +const PRODUCT_JSON_TWO_EXTENSIONS = `{ + "version": "1.0.0", + "trustedExtensionAuthAccess": [ + "redhat.yaml", + "redhat.openshift" + ] +}`; + +const PRODUCT_JSON_THREE_EXTENSIONS = `{ + "version": "1.0.0", + "trustedExtensionAuthAccess": [ + "redhat.yaml", + "redhat.openshift", + "devfile.vscode-devfile" + ] +}`; + +const PRODUCT_JSON_WITH_EXTENSIONS_ALTERNATIVE = `{ + "version": "1.0.0", + "trustedExtensionAuthAccess": { + "github": [ + "redhat.yaml" + ], + "gitlab": [ + "redhat.yaml", + "redhat.openshift" + ] + } +}`; + +describe('Test Configuring of Trusted Extensions Auth Access:', () => { + const originalReadFile = fs.readFile; + const originalWriteFile = fs.writeFile; + const originalConsoleLog = console.log; + + beforeEach(() => { + delete env.VSCODE_TRUSTED_EXTENSIONS; + + Object.assign(fs, { + readFile: originalReadFile, + writeFile: originalWriteFile, + }); + + Object.assign(console, { + log: originalConsoleLog, + }); + }); + + test('should skip if VSCODE_TRUSTED_EXTENSIONS is not set', async () => { + const readFileMock = jest.fn(); + Object.assign(fs, { + readFile: readFileMock, + writeFile: jest.fn(), + }); + + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(readFileMock).not.toHaveBeenCalled(); + }); + + test('should skip if VSCODE_TRUSTED_EXTENSIONS is empty', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = ''; + + const readFileMock = jest.fn(); + Object.assign(fs, { + readFile: readFileMock, + writeFile: jest.fn(), + }); + + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(readFileMock).not.toHaveBeenCalled(); + }); + + test('should skip if VSCODE_TRUSTED_EXTENSIONS has wrong value', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = ',,,'; + + const readFileMock = jest.fn(); + Object.assign(fs, { + readFile: readFileMock, + writeFile: jest.fn(), + }); + + const spy = jest.spyOn(console, 'log'); + + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(readFileMock).not.toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith('# Configuring Trusted Extensions...'); + expect(spy).toHaveBeenCalledWith(' > env.VSCODE_TRUSTED_EXTENSIONS is set to [,,,]'); + expect(spy).toHaveBeenCalledWith( + ' > ERROR: The variable provided most likely has wrong format. It should specify one or more extensions separated by comma.' + ); + }); + + test('should add new trustedExtensionAuthAccess section', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = ',,redhat.yaml,redhat.openshift'; + + let savedProductJson; + + Object.assign(fs, { + readFile: async (file: string) => { + if ('product.json' === file) { + return PRODUCT_JSON_SIMPLE; + } + }, + + writeFile: async (file: string, data: string) => { + if ('product.json' === file) { + savedProductJson = data; + } + }, + }); + + // test + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(savedProductJson).toBe(PRODUCT_JSON_TWO_EXTENSIONS); + }); + + test('should add extensions to existing trustedExtensionAuthAccess section', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = 'devfile.vscode-devfile,,'; + + let savedProductJson; + + Object.assign(fs, { + readFile: async (file: string) => { + if ('product.json' === file) { + return PRODUCT_JSON_TWO_EXTENSIONS; + } + }, + + writeFile: async (file: string, data: string) => { + if ('product.json' === file) { + savedProductJson = data; + } + }, + }); + + // test + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(savedProductJson).toBe(PRODUCT_JSON_THREE_EXTENSIONS); + }); + + test('should NOT add extensions to trustedExtensionAuthAccess section if extensions is already in the list', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = 'redhat.openshift'; + + const writeFileMock = jest.fn(); + Object.assign(fs, { + readFile: async (file: string) => { + if ('product.json' === file) { + return PRODUCT_JSON_TWO_EXTENSIONS; + } + }, + + writeFile: writeFileMock, + }); + + // test + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(writeFileMock).not.toHaveBeenCalled(); + }); + + test('should do nothing if trustedExtensionAuthAccess is object', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = 'devfile.vscode-devfile,redhat.vscode-xml'; + + const writeFileMock = jest.fn(); + Object.assign(fs, { + readFile: async (file: string) => { + if ('product.json' === file) { + return PRODUCT_JSON_WITH_EXTENSIONS_ALTERNATIVE; + } + }, + + writeFile: writeFileMock, + }); + + // test + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(writeFileMock).not.toHaveBeenCalled(); + }); + + test('should add only two extenions matching the regexp', async () => { + env.VSCODE_TRUSTED_EXTENSIONS = 'redhat.yaml,redhat.openshift,red hat.java'; + + let savedProductJson; + + Object.assign(fs, { + readFile: async (file: string) => { + if ('product.json' === file) { + return PRODUCT_JSON_SIMPLE; + } + }, + + writeFile: async (file: string, data: string) => { + if ('product.json' === file) { + savedProductJson = data; + } + }, + }); + + const spy = jest.spyOn(console, 'log'); + + // test + const trust = new TrustedExtensions(); + await trust.configure(); + + expect(savedProductJson).toBe(PRODUCT_JSON_TWO_EXTENSIONS); + + expect(spy).toHaveBeenCalledWith('# Configuring Trusted Extensions...'); + expect(spy).toHaveBeenCalledWith( + ' > env.VSCODE_TRUSTED_EXTENSIONS is set to [redhat.yaml,redhat.openshift,red hat.java]' + ); + expect(spy).toHaveBeenCalledWith(' > add redhat.yaml'); + expect(spy).toHaveBeenCalledWith(' > add redhat.openshift'); + expect(spy).toHaveBeenCalledWith(' > failure to add [red hat.java] because of wrong identifier'); + }); +});