diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index bf60ef30..c927d9e0 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -16,7 +16,10 @@ import axios, { AxiosRequestConfig } from "axios"; import { ProgressLocation, window } from "vscode"; import * as url from "url"; import { MetaInfoType, MetaObject, MetaObjectPayload } from "../models/meta"; -import { getCurrentToken } from "../services/kdbInsights/codeFlowLogin"; +import { + getCurrentToken, + getHttpsAgent, +} from "../services/kdbInsights/codeFlowLogin"; import { InsightsNode } from "../services/kdbTreeProvider"; import { GetDataObjectPayload } from "../models/data"; import { isCompressed, uncompress } from "../ipc/c"; @@ -54,6 +57,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ).then(async (token) => { this.connected = token ? true : false; if (token) { @@ -74,6 +78,27 @@ export class InsightsConnection { //will be added the feature to retrieve server objects from insights } + private async getOptions() { + const token = await getCurrentToken( + this.node.details.server, + this.node.details.alias, + this.node.details.realm || "insights", + !!this.node.details.insecure, + ); + + if (token === undefined) { + tokenUndefinedError(this.connLabel); + return undefined; + } + + const options: AxiosRequestConfig = { + headers: { Authorization: `Bearer ${token.accessToken}` }, + httpsAgent: getHttpsAgent(this.node.details.insecure), + }; + + return options; + } + public async getMeta(): Promise { if (this.connected) { const metaUrl = new url.URL( @@ -81,21 +106,12 @@ export class InsightsConnection { this.node.details.server, ); - const token = await getCurrentToken( - this.node.details.server, - this.node.details.alias, - this.node.details.realm || "insights", - ); + const options = await this.getOptions(); - if (token === undefined) { - tokenUndefinedError(this.connLabel); + if (options === undefined) { return undefined; } - const options = { - headers: { Authorization: `Bearer ${token.accessToken}` }, - }; - const metaResponse = await axios.post(metaUrl.toString(), {}, options); const meta: MetaObject = metaResponse.data; this.meta = meta; @@ -110,21 +126,13 @@ export class InsightsConnection { ext.insightsAuthUrls.configURL, this.node.details.server, ); - const token = await getCurrentToken( - this.node.details.server, - this.node.details.alias, - this.node.details.realm || "insights", - ); - if (token === undefined) { - tokenUndefinedError(this.connLabel); + const options = await this.getOptions(); + + if (options === undefined) { return undefined; } - const options = { - headers: { Authorization: `Bearer ${token.accessToken}` }, - }; - const configResponse = await axios.get(configUrl.toString(), options); this.config = configResponse.data; this.getInsightsVersion(); @@ -209,6 +217,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { tokenUndefinedError(this.connLabel); @@ -226,6 +235,7 @@ export class InsightsConnection { data: body, headers: headers, responseType: "arraybuffer", + httpsAgent: getHttpsAgent(this.node.details.insecure), }; const results = await window.withProgress( { @@ -315,6 +325,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { @@ -326,12 +337,13 @@ export class InsightsConnection { if (username === undefined || username.preferred_username === "") { invalidUsernameJWT(this.connLabel); } - const headers = { + const headers: AxiosRequestConfig = { headers: { Authorization: `Bearer ${token.accessToken}`, username: username.preferred_username!, json: true, }, + httpsAgent: getHttpsAgent(this.node.details.insecure), }; const body = { output: variableName, @@ -399,6 +411,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { tokenUndefinedError(this.connLabel); @@ -434,7 +447,10 @@ export class InsightsConnection { progress.report({ message: "Query is executing..." }); const spRes = await axios - .post(scratchpadURL.toString(), body, { headers }) + .post(scratchpadURL.toString(), body, { + headers, + httpsAgent: getHttpsAgent(this.node.details.insecure), + }) .then((response: any) => { kdbOutputLog(`[SCRATCHPAD] Status: ${response.status}`, "INFO"); if (isTableView && !response.data.error) { @@ -470,6 +486,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { @@ -482,12 +499,13 @@ export class InsightsConnection { invalidUsernameJWT(this.connLabel); return false; } - const headers = { + const headers: AxiosRequestConfig = { headers: { Authorization: `Bearer ${token.accessToken}`, username: username.preferred_username!, json: true, }, + httpsAgent: getHttpsAgent(this.node.details.insecure), }; return await window.withProgress( { diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 19ead07e..0f8e87a9 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -94,21 +94,27 @@ export async function addInsightsConnection(insightsData: InsightDetails) { return; } else { const key = insightsData.alias; + let server = insightsData.server || ""; + if (!/^https?:\/\//i.exec(server)) { + server = "https://" + server; + } if (insights === undefined) { insights = { key: { auth: true, alias: insightsData.alias, - server: insightsData.server!, + server, realm: insightsData.realm, + insecure: insightsData.insecure, }, }; } else { insights[key] = { auth: true, alias: insightsData.alias, - server: insightsData.server!, + server, realm: insightsData.realm, + insecure: insightsData.insecure, }; } diff --git a/src/models/insights.ts b/src/models/insights.ts index bccf6667..b9446e4d 100644 --- a/src/models/insights.ts +++ b/src/models/insights.ts @@ -16,6 +16,7 @@ export interface InsightDetails { server: string; auth: boolean; realm?: string; + insecure?: boolean; } export interface Insights { diff --git a/src/services/kdbInsights/codeFlowLogin.ts b/src/services/kdbInsights/codeFlowLogin.ts index 33c344b1..faef4ca7 100644 --- a/src/services/kdbInsights/codeFlowLogin.ts +++ b/src/services/kdbInsights/codeFlowLogin.ts @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -import axios from "axios"; +import axios, { AxiosRequestConfig } from "axios"; import * as crypto from "crypto"; import * as fs from "fs-extra"; import * as http from "http"; @@ -21,6 +21,7 @@ import * as url from "url"; import { ext } from "../../extensionVariables"; import { Uri, env } from "vscode"; import { pickPort } from "pick-port"; +import https from "https"; interface IDeferred { resolve: (result: T | Promise) => void; @@ -55,6 +56,10 @@ export interface IToken { refreshToken: string; } +export function getHttpsAgent(insecure: boolean | undefined) { + return new https.Agent({ rejectUnauthorized: !insecure }); +} + const defaultTimeout = 3 * 60 * 1000; // 3 min const closeTimeout = 10 * 1000; // 10 sec @@ -62,7 +67,11 @@ const commonRequestParams = { client_id: "insights-app", }; -export async function signIn(insightsUrl: string, realm: string) { +export async function signIn( + insightsUrl: string, + realm: string, + insecure: boolean, +) { const { server, codePromise } = createServer(); try { @@ -82,7 +91,7 @@ export async function signIn(insightsUrl: string, realm: string) { await env.openExternal(Uri.parse(authorizationUrl.toString())); const code = await codePromise; - return await getToken(insightsUrl, realm, code); + return await getToken(insightsUrl, realm, insecure, code); } finally { setImmediate(() => server.close()); } @@ -91,6 +100,7 @@ export async function signIn(insightsUrl: string, realm: string) { export async function signOut( insightsUrl: string, realm: string, + insecure: boolean, token: string, ): Promise { const queryParams = queryString({ @@ -100,8 +110,9 @@ export async function signOut( const body = { body: queryParams, }; - const headers = { + const headers: AxiosRequestConfig = { headers: { "Content-Type": "application/x-www-form-urlencoded" }, + httpsAgent: getHttpsAgent(insecure), }; const requestUrl = getRevokeUrl(insightsUrl, realm); @@ -113,9 +124,10 @@ export async function signOut( export async function refreshToken( insightsUrl: string, realm: string, + insecure: boolean, token: string, ): Promise { - return await tokenRequest(insightsUrl, realm, { + return await tokenRequest(insightsUrl, realm, insecure, { grant_type: ext.insightsGrantType.refreshToken, refresh_token: token, }); @@ -126,6 +138,7 @@ export async function getCurrentToken( serverName: string, serverAlias: string, realm: string, + insecure: boolean, ): Promise { if (serverName === "" || serverAlias === "") { return undefined; @@ -137,9 +150,14 @@ export async function getCurrentToken( if (existingToken !== undefined) { const storedToken: IToken = JSON.parse(existingToken); if (new Date(storedToken.accessTokenExpirationDate) < new Date()) { - token = await refreshToken(serverName, realm, storedToken.refreshToken); + token = await refreshToken( + serverName, + realm, + insecure, + storedToken.refreshToken, + ); if (token === undefined) { - token = await signIn(serverName, realm); + token = await signIn(serverName, realm, insecure); ext.context.secrets.store(serverAlias, JSON.stringify(token)); } ext.context.secrets.store(serverAlias, JSON.stringify(token)); @@ -148,7 +166,7 @@ export async function getCurrentToken( return storedToken; } } else { - token = await signIn(serverName, realm); + token = await signIn(serverName, realm, insecure); ext.context.secrets.store(serverAlias, JSON.stringify(token)); } return token; @@ -158,9 +176,10 @@ export async function getCurrentToken( async function getToken( insightsUrl: string, realm: string, + insecure: boolean, code: string, ): Promise { - return await tokenRequest(insightsUrl, realm, { + return await tokenRequest(insightsUrl, realm, insecure, { code, grant_type: ext.insightsGrantType.authorizationCode, }); @@ -169,15 +188,17 @@ async function getToken( async function tokenRequest( insightsUrl: string, realm: string, + insecure: boolean, params: any, ): Promise { const queryParams = queryString(params); - const headers = { + const headers: AxiosRequestConfig = { headers: { "Content-Type": "application/x-www-form-urlencoded", }, timeout: closeTimeout, signal: AbortSignal.timeout(closeTimeout), + httpsAgent: getHttpsAgent(insecure), }; const requestUrl = getTokenUrl(insightsUrl, realm); diff --git a/src/webview/components/kdbNewConnectionView.ts b/src/webview/components/kdbNewConnectionView.ts index 63455338..68e98c78 100644 --- a/src/webview/components/kdbNewConnectionView.ts +++ b/src/webview/components/kdbNewConnectionView.ts @@ -53,6 +53,7 @@ export class KdbNewConnectionView extends LitElement { server: "", auth: true, realm: "", + insecure: false, }; this.bundledServer = { serverName: "127.0.0.1", @@ -410,6 +411,15 @@ export class KdbNewConnectionView extends LitElement {
Advanced ${this.renderRealm()} +
+ Accept insecure SSL certifcates +
diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 71163bc5..2be3b0c3 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -726,13 +726,18 @@ describe("Code flow login service tests", () => { it("Should return a correct login", async () => { sinon.stub(codeFlow, "signIn").returns(token); - const result = await signIn("http://localhost", "insights"); + const result = await signIn("http://localhost", "insights", false); assert.strictEqual(result, token, "Invalid token returned"); }); it("Should execute a correct logout", async () => { sinon.stub(axios, "post").resolves(Promise.resolve({ data: token })); - const result = await signOut("http://localhost", "insights", "token"); + const result = await signOut( + "http://localhost", + "insights", + false, + "token", + ); assert.strictEqual(result, undefined, "Invalid response from logout"); }); @@ -741,6 +746,7 @@ describe("Code flow login service tests", () => { const result = await refreshToken( "http://localhost", "insights", + false, JSON.stringify(token), ); assert.strictEqual( @@ -751,7 +757,7 @@ describe("Code flow login service tests", () => { }); it("Should not return token from secret store", async () => { - const result = await getCurrentToken("", "testalias", "insights"); + const result = await getCurrentToken("", "testalias", "insights", false); assert.strictEqual( result, undefined, @@ -760,7 +766,7 @@ describe("Code flow login service tests", () => { }); it("Should not return token from secret store", async () => { - const result = await getCurrentToken("testserver", "", "insights"); + const result = await getCurrentToken("testserver", "", "insights", false); assert.strictEqual( result, undefined, @@ -772,7 +778,7 @@ describe("Code flow login service tests", () => { sinon.stub(env, "openExternal").value(async () => { throw new Error(); }); - await assert.rejects(() => signIn("http://127.0.0.1", "insights")); + await assert.rejects(() => signIn("http://127.0.0.1", "insights", false)); }); }); diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index 740d8948..b63e8f21 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -480,6 +480,7 @@ describe("KdbNewConnectionView", () => { server: "", auth: true, realm: "", + insecure: false, }; const data = view["data"]; assert.deepEqual(data, expectedData);