diff --git a/package-lock.json b/package-lock.json index 86958066..e61338af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1667,7 +1667,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -5615,7 +5616,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -6128,8 +6130,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.merge": { "version": "4.6.2", @@ -7347,6 +7348,12 @@ } } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -7368,12 +7375,6 @@ "strip-ansi": "^6.0.0" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7653,6 +7654,14 @@ } } }, + "ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", + "requires": { + "lodash": "^4.17.5" + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -7982,7 +7991,8 @@ "version": "7.5.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 3170f8ee..e381d52c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "README.md" ], "dependencies": { - "log4js": "^6.9.1" + "log4js": "^6.9.1", + "ts-mockito": "^2.6.1" }, "scripts": { "build": "tsc", diff --git a/src/main/chat/CxChat.ts b/src/main/chat/CxChat.ts new file mode 100644 index 00000000..a83235a9 --- /dev/null +++ b/src/main/chat/CxChat.ts @@ -0,0 +1,13 @@ +export default class CxChat { + conversationId: string; + responses: string[]; + + constructor(conversationId: string, responses: string[]) { + this.conversationId = conversationId; + this.responses = responses; + } + + static parseChat(resultObject: any): CxChat { + return new CxChat(resultObject.conversationId, resultObject.response); + } +} diff --git a/src/main/mask/CxMask.ts b/src/main/mask/CxMask.ts new file mode 100644 index 00000000..86525c6c --- /dev/null +++ b/src/main/mask/CxMask.ts @@ -0,0 +1,15 @@ +import CxMaskedSecret from "./CxMaskedSecret"; + +export default class CxMask { + maskedSecrets: CxMaskedSecret[]; + maskedFile: string; + + constructor(maskedSecrets: CxMaskedSecret[], maskedFile: string) { + this.maskedSecrets = maskedSecrets; + this.maskedFile = maskedFile; + } + + static parseMask(resultObject: any): CxMask { + return new CxMask(resultObject.maskedSecrets,resultObject.maskedFile); + } +} diff --git a/src/main/mask/CxMaskedSecret.ts b/src/main/mask/CxMaskedSecret.ts new file mode 100644 index 00000000..013ae5c8 --- /dev/null +++ b/src/main/mask/CxMaskedSecret.ts @@ -0,0 +1,11 @@ +export default class CxMaskedSecret { + masked: string; + secret: string; + line: number; + + constructor(masked: string, secret: string, line: number) { + this.masked = masked; + this.secret = secret; + this.line = line; + } +} diff --git a/src/main/wrapper/CxConstants.ts b/src/main/wrapper/CxConstants.ts index 9c69a086..200a658a 100644 --- a/src/main/wrapper/CxConstants.ts +++ b/src/main/wrapper/CxConstants.ts @@ -43,6 +43,16 @@ export enum CxConstants { CMD_KICS_REALTIME = "kics-realtime", CMD_SCA_REALTIME = "sca-realtime", CMD_SCA_REALTIME_PROJECT_DIR = "--project-dir", + CMD_CHAT = "chat", + CMD_CHAT_APIKEY = "--chat-apikey", + CMD_CHAT_FILE = "--result-file", + CMD_CHAT_LINE = "--result-line", + CMD_CHAT_SEVERITY = "--result-severity", + CMD_CHAT_VULNERABILITY = "--result-vulnerability", + CMD_CHAT_INPUT = "--user-input", + CMD_CHAT_CONVERSATION_ID = "--conversation-id", + CMD_CHAT_MODEL = "--model", + CMD_MASK_SECRETS = "mask", SCAN_INFO_FORMAT = "--scan-info-format", FORMAT = "--format", FORMAT_JSON = "json", @@ -70,6 +80,8 @@ export enum CxConstants { CODE_BASHING_TYPE = "CxCodeBashing", KICS_REALTIME_TYPE = "CxKicsRealTime", SCA_REALTIME_TYPE = "CxScaRealTime", + CHAT_TYPE = "CxChat", + MASK_TYPE = "CxMask", LEARN_MORE_DESCRIPTIONS_TYPE = "CxLearnMoreDescriptions", KICS_REMEDIATION_TYPE = "CxKicsRemediation", BFL_TYPE = "CxBFL", @@ -81,5 +93,6 @@ export enum CxConstants { SEVERITY_MEDIUM = "medium", STATE_CONFIRMED = "confirmed", CMD_LEARN_MORE = "learn-more", - IDE_SCANS_KEY = " scan.config.plugins.ideScans" + IDE_SCANS_KEY = " scan.config.plugins.ideScans", + AI_GUIDED_REMEDIATION_KEY = " scan.config.plugins.aiGuidedRemediation" } diff --git a/src/main/wrapper/CxWrapper.ts b/src/main/wrapper/CxWrapper.ts index 7d94f3f8..b6cb84ad 100644 --- a/src/main/wrapper/CxWrapper.ts +++ b/src/main/wrapper/CxWrapper.ts @@ -299,6 +299,45 @@ export class CxWrapper { return output.has(CxConstants.IDE_SCANS_KEY) && output.get(CxConstants.IDE_SCANS_KEY).toLowerCase() === " true"; } + async guidedRemediationEnabled() : Promise { + const commands: string[] = [CxConstants.CMD_UTILS, CxConstants.SUB_CMD_TENANT]; + commands.push(...this.initializeCommands(false)); + const exec = new ExecutionService(); + const output = await exec.executeMapTenantOutputCommands(this.config.pathToExecutable, commands); + return output.has(CxConstants.AI_GUIDED_REMEDIATION_KEY) && output.get(CxConstants.AI_GUIDED_REMEDIATION_KEY).toLowerCase() === " true"; + } + + async chat(apikey: string, file: string, line: number, severity: string, vulnerability: string, input: string, conversationId?: string, model?: string): Promise { + const commands: string[] = [ + CxConstants.CMD_CHAT, + CxConstants.CMD_CHAT_APIKEY, apikey, + CxConstants.CMD_CHAT_FILE, file, + CxConstants.CMD_CHAT_LINE, line.toString(), + CxConstants.CMD_CHAT_SEVERITY, severity, + CxConstants.CMD_CHAT_VULNERABILITY, vulnerability, + CxConstants.CMD_CHAT_INPUT, input, + ]; + if (conversationId) { + commands.push(CxConstants.CMD_CHAT_CONVERSATION_ID, conversationId) + } + if (model) { + commands.push(CxConstants.CMD_CHAT_MODEL, model) + } + commands.push(...this.initializeCommands(false)); + return new ExecutionService().executeCommands(this.config.pathToExecutable, commands, CxConstants.CHAT_TYPE); + } + + async maskSecrets( file: string): Promise { + const commands: string[] = [ + CxConstants.CMD_UTILS, + CxConstants.CMD_MASK_SECRETS, + CxConstants.CMD_CHAT_FILE, file, + ]; + + commands.push(...this.initializeCommands(false)); + return new ExecutionService().executeCommands(this.config.pathToExecutable, commands, CxConstants.MASK_TYPE); + } + prepareAdditionalParams(additionalParameters: string) : string[] { const params: string[] = []; diff --git a/src/main/wrapper/ExecutionService.ts b/src/main/wrapper/ExecutionService.ts index 1da7ca9f..e56b5168 100644 --- a/src/main/wrapper/ExecutionService.ts +++ b/src/main/wrapper/ExecutionService.ts @@ -20,6 +20,8 @@ import CxNode from "../results/CxNode"; import CxPackageData from "../results/CxPackageData"; import CxKicsRemediation from "../remediation/CxKicsRemediation"; import CxScaRealTime from "../scaRealtime/CxScaRealTime"; +import CxChat from "../chat/CxChat"; +import CxMask from "../mask/CxMask"; function isJsonString(s: string) { @@ -206,10 +208,18 @@ export class ExecutionService { cxCommandOutput.payload = learnMore; break; case CxConstants.KICS_REMEDIATION_TYPE: - const kicsRemediationOutput = CxKicsRemediation.parseKicsRemediation(resultObject) - cxCommandOutput.payload = [kicsRemediationOutput] + const kicsRemediationOutput = CxKicsRemediation.parseKicsRemediation(resultObject); + cxCommandOutput.payload = [kicsRemediationOutput]; break; - default: + case CxConstants.CHAT_TYPE: + const chatOutput = CxChat.parseChat(resultObject); + cxCommandOutput.payload = [chatOutput]; + break; + case CxConstants.MASK_TYPE: + const maskOutput = CxMask.parseMask(resultObject); + cxCommandOutput.payload = [maskOutput]; + break; + default: cxCommandOutput.payload = resultObject; } } diff --git a/src/tests/ChatTest.test.ts b/src/tests/ChatTest.test.ts new file mode 100644 index 00000000..482265df --- /dev/null +++ b/src/tests/ChatTest.test.ts @@ -0,0 +1,55 @@ +import {CxWrapper} from '../main/wrapper/CxWrapper'; +import {CxCommandOutput} from "../main/wrapper/CxCommandOutput"; +import CxChat from "../main/chat/CxChat"; +import {anything, instance, mock, when} from 'ts-mockito'; +import {BaseTest} from "./BaseTest"; + +function createOutput(exitCode:number,payload:CxChat):CxCommandOutput { + const output = new CxCommandOutput(); + output.exitCode=exitCode; + output.status=""; + output.payload=[payload]; + return output; +} + +describe("Gpt Chat Cases", () => { + // tests preparation + const cxScanConfig = new BaseTest(); + const mockedWrapper: CxWrapper = mock(CxWrapper); + const originalWrapper: CxWrapper = new CxWrapper(cxScanConfig); + const outputSuccessful = createOutput(0,new CxChat("CONVERSATION",["RESPONSE"] )); + + when(mockedWrapper.chat("APIKEY","FILE",anything(),anything(),anything(),anything(),anything(), anything())).thenResolve( + outputSuccessful + ); + const wrapper: CxWrapper = instance(mockedWrapper); + + it('Gpt Chat Successful case', async () => { + const cxCommandOutput = await wrapper.chat( + "APIKEY", + "FILE", + 0, + "SEVERITY", + "VULNERABILITY", + "INPUT", + "CONVERSATION", + "MODEL" + ); + expect(cxCommandOutput.exitCode).toBe(0); + }); + + it('Gpt Chat Failed case', async () => { + const cxCommandOutput = await originalWrapper.chat( + "APIKEY", + "FILE", + 0, + "SEVERITY", + "VULNERABILITY", + "INPUT", + "", + "MODEL" + ); + expect(cxCommandOutput.exitCode).toBe(0); + expect(cxCommandOutput.payload[0].responses[0]).toBe("It seems that FILE is not available for AI Guided Remediation. Please ensure that you have opened the correct workspace or the relevant file."); + }); +}); \ No newline at end of file diff --git a/src/tests/MaskTest.test.ts b/src/tests/MaskTest.test.ts new file mode 100644 index 00000000..18f23b11 --- /dev/null +++ b/src/tests/MaskTest.test.ts @@ -0,0 +1,14 @@ +import {CxWrapper} from '../main/wrapper/CxWrapper'; +import {CxCommandOutput} from "../main/wrapper/CxCommandOutput"; +import {BaseTest} from "./BaseTest"; + +describe("Mask cases",() => { + const cxScanConfig = new BaseTest(); + it('Mask Successful case', async () => { + const auth = new CxWrapper(cxScanConfig); + const data = await auth.maskSecrets("dist/tests/data/package.json") + const cxCommandOutput: CxCommandOutput = data; + expect(cxCommandOutput.payload.length).toEqual(1); + }); + +}); \ No newline at end of file diff --git a/src/tests/ScanTest.test.ts b/src/tests/ScanTest.test.ts index 028531cf..41007d3c 100644 --- a/src/tests/ScanTest.test.ts +++ b/src/tests/ScanTest.test.ts @@ -112,8 +112,17 @@ describe("ScanCreate cases", () => { }) it("Should check if scan create is possible", async() => { + const cxScanConfig = new BaseTest(); const auth = new CxWrapper(cxScanConfig); const tenantSettings: boolean = await auth.ideScansEnabled(); expect(tenantSettings).toBeDefined(); }) + + it("Should check if AI guided remediation is active", async() => { + const cxScanConfig = new BaseTest(); + const auth = new CxWrapper(cxScanConfig); + const aiEnabled: boolean = await auth.guidedRemediationEnabled(); + expect(aiEnabled).toBeDefined(); + }) + }); \ No newline at end of file