Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(git log *)",
"Bash(git pull *)",
"Bash(git stash *)"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

public abstract class BaseUITest {

private static final String INFO_SUCCESSFUL_CONNECTION = "Successfully authenticated to Checkmarx One server!";
private static final String INFO_SUCCESSFUL_CONNECTION = "You are connected to Checkmarx One";

protected static final String ASSERT_FILTER_ACTIONS_IN_TOOLBAR = "All filter actions must be in the tool bar";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.slf4j.Logger;

import com.checkmarx.ast.wrapper.CxException;
import com.checkmarx.ast.wrapper.CxWrapper;
import com.checkmarx.eclipse.runner.Authenticator;
import com.checkmarx.eclipse.utils.CxLogger;
import com.checkmarx.eclipse.utils.PluginConstants;

class AuthenticatorTest {
Expand All @@ -24,14 +26,15 @@ void testDoAuthenticationSuccess() throws Exception {

try (MockedConstruction<CxWrapper> mocked =
Mockito.mockConstruction(CxWrapper.class,
(mock, context) -> when(mock.authValidate()).thenReturn("SUCCESS"))) {
(mock, context) -> when(mock.authValidate()).thenReturn("SUCCESS"));
MockedStatic<CxLogger> mockedCxLogger = Mockito.mockStatic(CxLogger.class)) {

Authenticator authenticator = new Authenticator(mockLogger);

String result = authenticator.doAuthentication("dummyKey", "--param");

assertEquals("SUCCESS", result);
verify(mockLogger).info("Authentication Status: SUCCESS");
mockedCxLogger.verify(() -> CxLogger.info(String.format(PluginConstants.INFO_AUTHENTICATION_STATUS, "SUCCESS")));
}
}

Expand All @@ -43,17 +46,18 @@ void testDoAuthenticationIOException() throws Exception {
try (MockedConstruction<CxWrapper> mocked =
Mockito.mockConstruction(CxWrapper.class,
(mock, context) -> when(mock.authValidate())
.thenThrow(new IOException("IO error")))) {
.thenThrow(new IOException("IO error")));
MockedStatic<CxLogger> mockedCxLogger = Mockito.mockStatic(CxLogger.class)) {

Authenticator authenticator = new Authenticator(mockLogger);

String result = authenticator.doAuthentication("dummyKey", "--param");

assertEquals("IO error", result);
verify(mockLogger).error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "IO error")),
any(IOException.class)
);
mockedCxLogger.verify(() -> CxLogger.error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "IO error")),
any(IOException.class)
));
}
}

Expand All @@ -65,17 +69,18 @@ void testDoAuthenticationInterruptedException() throws Exception {
try (MockedConstruction<CxWrapper> mocked =
Mockito.mockConstruction(CxWrapper.class,
(mock, context) -> when(mock.authValidate())
.thenThrow(new InterruptedException("Interrupted")))) {
.thenThrow(new InterruptedException("Interrupted")));
MockedStatic<CxLogger> mockedCxLogger = Mockito.mockStatic(CxLogger.class)) {

Authenticator authenticator = new Authenticator(mockLogger);

String result = authenticator.doAuthentication("dummyKey", "--param");

assertEquals("Interrupted", result);
verify(mockLogger).error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "Interrupted")),
any(InterruptedException.class)
);
mockedCxLogger.verify(() -> CxLogger.error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "Interrupted")),
any(InterruptedException.class)
));
}
}

Expand All @@ -87,17 +92,18 @@ void testDoAuthenticationCxException() throws Exception {
try (MockedConstruction<CxWrapper> mocked =
Mockito.mockConstruction(CxWrapper.class,
(mock, context) -> when(mock.authValidate())
.thenThrow(new CxException(1, "Cx error")))) {
.thenThrow(new CxException(1, "Cx error")));
MockedStatic<CxLogger> mockedCxLogger = Mockito.mockStatic(CxLogger.class)) {

Authenticator authenticator = new Authenticator(mockLogger);

String result = authenticator.doAuthentication("dummyKey", "--param");

assertEquals("Cx error", result);
verify(mockLogger).error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "Cx error")),
any(CxException.class)
);
mockedCxLogger.verify(() -> CxLogger.error(
eq(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, "Cx error")),
any(CxException.class)
));
}
}

Expand Down
4 changes: 4 additions & 0 deletions checkmarx-ast-eclipse-plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@
</perspectiveExtension>
</extension>

<extension point="org.eclipse.ui.startup">
<startup class="com.checkmarx.eclipse.startup.PluginStartup"/>
</extension>

</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,23 @@ public void widgetSelected(SelectionEvent e) {
return t.getMessage();
}
}).thenAccept((result) -> Display.getDefault().syncExec(() -> {
connectionLabel.setText(result);
connectionLabel.setText(mapAuthResult(result));
getFieldEditorParent().layout();
connectionButton.setEnabled(true);
}));
}
});
}



private static String mapAuthResult(String result) {
if (result != null && result.contains(PluginConstants.AUTH_SUCCESS_PATTERN)) {
return PluginConstants.AUTH_SUCCESS_DISPLAY;
}
return result;
}

private FieldEditor space() {
return new LabelFieldEditor("", getFieldEditorParent());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public String doAuthentication(String apiKey, String additionalParams) {
try {
CxWrapper wrapper = new CxWrapper(config, log);
String cxValidateOutput = wrapper.authValidate();
log.info(AUTH_STATUS + cxValidateOutput);
CxLogger.info(String.format(PluginConstants.INFO_AUTHENTICATION_STATUS, cxValidateOutput));
return cxValidateOutput;
} catch (IOException | InterruptedException | CxException e) {
log.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, e.getMessage()), e);
CxLogger.error(String.format(PluginConstants.ERROR_AUTHENTICATING_AST, e.getMessage()), e);
return e.getMessage();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.checkmarx.eclipse.startup;

import org.eclipse.ui.IStartup;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;

import com.checkmarx.eclipse.utils.CxLogger;

public class PluginStartup implements IStartup {

private static final String VIEW_ID = "com.checkmarx.eclipse.views.CheckmarxView";

@Override
public void earlyStartup() {
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
try {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
if (page != null && page.findView(VIEW_ID) == null) {
page.showView(VIEW_ID);
}
}
} catch (PartInitException e) {
CxLogger.error("Failed to open Checkmarx One view on startup: " + e.getMessage(), e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class PluginConstants {
public static final String INFO_CHANGE_SCAN_EVENT_NOT_TRIGGERED = "Change scan id event not triggered. Request already running: %s. Scan id results already retrieved: %s";
public static final String INFO_CHANGE_BRANCH_EVENT_NOT_TRIGGERED = "Change branch event not triggered. Branch already selected";
public static final String INFO_CHANGE_PROJECT_EVENT_NOT_TRIGGERED = "Change project event not triggered. Project already selected";
public static final String AUTH_SUCCESS_PATTERN = "Successfully authenticated";
public static final String AUTH_SUCCESS_DISPLAY = "You are connected to Checkmarx One";

/******************************** TREE MESSAGES ********************************/
public static final String TREE_INVALID_SCAN_ID_FORMAT = "Invalid scan id format.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
Expand Down Expand Up @@ -160,6 +164,7 @@ public class CheckmarxView extends ViewPart implements EventHandler {
private Text commentText;
private DisplayModel rootModel;
private String selectedSeverity, selectedState;
private DisplayModel currentlyDisplayedItem;
private Button triageButton;
private SelectionAdapter triageButtonAdapter, codeBashingAdapter;
private Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
Expand Down Expand Up @@ -626,12 +631,29 @@ private void createResultViewPanel(Composite resultsComposite) {
combo_1.setLayoutData(gd_combo_1);

triageStateComboViewer = new ComboViewer(triageView, SWT.READ_ONLY);
triageStateComboViewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
String s = element instanceof String ? (String) element : super.getText(element);
return s.length() > 50 ? s.substring(0, 47) + "..." : s;
}
});
Combo combo_2 = triageStateComboViewer.getCombo();
combo_2.setEnabled(true);
combo_2.setData(PluginConstants.DATA_ID_KEY, PluginConstants.TRIAGE_STATE_COMBO_ID);
GridData gd_combo_2 = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
// gd_combo_2.widthHint = 180;
combo_2.setLayoutData(gd_combo_2);
combo_2.addMouseTrackListener(new MouseTrackAdapter() {
@Override
public void mouseHover(MouseEvent e) {
IStructuredSelection sel = (IStructuredSelection) triageStateComboViewer.getSelection();
if (!sel.isEmpty()) {
String fullName = (String) sel.getFirstElement();
combo_2.setToolTipText(fullName.length() > 50 ? fullName : "");
}
}
});

triageButton = new Button(triageView, SWT.FLAT | SWT.CENTER);
triageButton.setData(PluginConstants.DATA_ID_KEY, PluginConstants.TRIAGE_BUTTON_ID);
Expand Down Expand Up @@ -904,7 +926,26 @@ protected IStatus run(IProgressMonitor monitor) {
debounceTimer.schedule(pendingSearchTask, DEBOUNCE_DELAY_MS);

}
});
});

// Add FocusListener to disable branch combo when project is cleared and focus lost
projectComboViewer.getCombo().addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
// When user clicks outside project combo, check if project is empty
String enteredProject = projectComboViewer.getCombo().getText().trim();
// If project field is empty or contains only the placeholder text, disable branch combo
if (enteredProject.isEmpty() || enteredProject.equals(PROJECT_COMBO_VIEWER_TEXT)) {
currentProjectId = PluginConstants.EMPTY_STRING;
PluginUtils.enableComboViewer(branchComboViewer, false);
}
}

@Override
public void focusGained(FocusEvent e) {
// No action needed on focus gain
}
});

}
/**
Expand Down Expand Up @@ -1082,8 +1123,7 @@ private void createScanIdComboBox(Composite parent) {
scanIdComboViewer.setContentProvider(ArrayContentProvider.getInstance());
scanIdComboViewer.setInput(new ArrayList<>());

GridData gridData = new GridData();
gridData.widthHint = 520;
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
scanIdComboViewer.getCombo().setLayoutData(gridData);

scanIdComboViewer.getCombo().addListener(SWT.DefaultSelection, new Listener() {
Expand Down Expand Up @@ -1264,13 +1304,31 @@ protected IStatus run(IProgressMonitor arg0) {
List<Project> projectList = getProjects();
if (projectList.isEmpty())
return null;
String projectName = getProjectFromId(projectList, projectId);

// Fetch the project directly by ID — the full list may not contain it (e.g. pagination limits)
Project fetchedProject = DataProvider.getInstance().getProjectById(projectId);

// Determine project name: prefer the directly-fetched result, fall back to list lookup
String projectName = (fetchedProject != null)
? fetchedProject.getName()
: getProjectFromId(projectList, projectId);

// If the project was not already in the list, prepend it so it's visible in the dropdown
if (fetchedProject != null && projectList.stream().noneMatch(p -> p.getId().equals(projectId))) {
projectList = new ArrayList<>(projectList);
projectList.add(0, fetchedProject);
}
final List<Project> finalProjectList = projectList;

currentProjectId = projectId;
GlobalSettings.storeInPreferences(GlobalSettings.PARAM_PROJECT_ID, currentProjectId);

sync.asyncExec(() -> {
projectComboViewer.setInput(projectList);
currentProjects = finalProjectList;
storeCurrentProjects = finalProjectList;
projectComboViewer.setInput(finalProjectList);
PluginUtils.setTextForComboViewer(projectComboViewer, projectName);
PluginUtils.enableComboViewer(projectComboViewer, true);
setSelectionForBranchComboViewer(scan.getBranch(), projectId);
setSelectionForScanIdComboViewer(scan.getId(), scan.getBranch());
updateStartScanButton(true);
Expand Down Expand Up @@ -1386,6 +1444,7 @@ protected IStatus run(IProgressMonitor arg0) {

if (selectedItem.getResult() != null && selectedItem.getResult().getSimilarityId() != null) {
sync.asyncExec(() -> {
currentlyDisplayedItem = selectedItem;
createTriageSeverityAndStateCombos(selectedItem);
populateTriageChanges(selectedItem);
resultViewComposite.setVisible(true);
Expand Down Expand Up @@ -2473,6 +2532,8 @@ private Image findSeverityImage(DisplayModel model) {
private void listener(PluginListenerDefinition definition) {
switch (definition.getListenerType()) {
case FILTER_CHANGED:
updateResultsTree(definition.getResutls(), true);
break;
case GET_RESULTS:
updateResultsTree(definition.getResutls(), false);
break;
Expand All @@ -2489,9 +2550,15 @@ private void listener(PluginListenerDefinition definition) {

private void updateResultsTree(List<DisplayModel> results, boolean expand) {
sync.asyncExec(() -> {
if (currentlyDisplayedItem == null
|| currentlyDisplayedItem.getSeverity() == null
|| !FilterState.isSeverityEnabled(currentlyDisplayedItem.getSeverity())) {
resultViewComposite.setVisible(false);
attackVectorCompositePanel.setVisible(false);
}
Object[] expanded = resultsTree.getExpandedElements();
rootModel.children.clear();
rootModel.children.addAll(results);
Object[] expanded = resultsTree.getExpandedElements();
resultsTree.refresh();
if (expand) {
Set<String> expandedDMNames = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,27 @@ public List<Project> getProjects() throws Exception {
return projectList;
}

/**
* Fetch a single project directly by its ID using the project show command.
* Returns null if the project cannot be retrieved.
*/
public Project getProjectById(String projectId) {
try {
CxWrapper cxWrapper = getWrapper();
if (cxWrapper != null && projectId != null && !projectId.isEmpty()) {
return cxWrapper.projectShow(UUID.fromString(projectId));
}
} catch (Exception e) {
CxLogger.error(String.format(PluginConstants.ERROR_GETTING_PROJECTS, e.getMessage()), e);
}
return null;
}

/**
* Get One projects filtered by name
*
*
* @return
* @throws Exception
* @throws Exception
*/
public List<Project> getProjects(String projectName) throws Exception {
List<Project> projectList = new ArrayList<Project>();
Expand Down
Loading
Loading