diff --git a/CHANGELOG.md b/CHANGELOG.md index c6706c5..7d58047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [2.0.0] - Unreleased ### Changes +- add folder trust feature + +## [2.0.0] - v20221115.132308 +### Changes - adds configuration wizard for custom endpoints ## [2.0.0] - v20221007.135736 diff --git a/plugin/META-INF/MANIFEST.MF b/plugin/META-INF/MANIFEST.MF index 3d751ed..7e32b93 100644 --- a/plugin/META-INF/MANIFEST.MF +++ b/plugin/META-INF/MANIFEST.MF @@ -10,7 +10,7 @@ Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.jdt.core, org.eclipse.core.resources, - org.eclipse.lsp4e;bundle-version="0.13.9", + org.eclipse.lsp4e;bundle-version="[0.13.9,0.14.0.qualifier]", org.eclipse.lsp4e.jdt;bundle-version="0.10.1", org.eclipse.equinox.security, org.eclipse.equinox.security.ui, @@ -33,5 +33,5 @@ Bundle-ClassPath: ., target/dependency/httpcore-4.4.15.jar, target/dependency/jackson-annotations-2.13.4.jar, target/dependency/jackson-core-2.13.4.jar, - target/dependency/jackson-databind-2.13.4.jar, + target/dependency/jackson-databind-2.13.4.2.jar, target/dependency/javax.inject-1.jar diff --git a/plugin/build.properties b/plugin/build.properties index d38ca1b..44c1bbd 100644 --- a/plugin/build.properties +++ b/plugin/build.properties @@ -14,7 +14,7 @@ bin.includes = plugin.xml,\ target/dependency/httpcore-4.4.15.jar,\ target/dependency/jackson-annotations-2.13.4.jar,\ target/dependency/jackson-core-2.13.4.jar,\ - target/dependency/jackson-databind-2.13.4.jar,\ + target/dependency/jackson-databind-2.13.4.2.jar,\ target/dependency/javax.inject-1.jar src.includes =src/,\ icons/ diff --git a/plugin/io.snyk.eclipse.plugin.eml b/plugin/io.snyk.eclipse.plugin.eml index 5ef4faa..ecca7d7 100644 --- a/plugin/io.snyk.eclipse.plugin.eml +++ b/plugin/io.snyk.eclipse.plugin.eml @@ -19,22 +19,22 @@ - + - + - - + + - - - + + + diff --git a/plugin/pom.xml b/plugin/pom.xml index 5584584..6870cb6 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -29,7 +29,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4 + 2.13.4.2 jar diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/PreferencesPage.java b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/PreferencesPage.java index 2960692..a3f7943 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/PreferencesPage.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/PreferencesPage.java @@ -8,6 +8,9 @@ import io.snyk.languageserver.LsRuntimeEnvironment; import io.snyk.languageserver.download.HttpClientFactory; import io.snyk.languageserver.download.LsBinaries; + +import java.io.File; + import org.eclipse.core.net.proxy.IProxyData; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.FieldEditor; @@ -19,103 +22,114 @@ import org.eclipse.ui.IWorkbenchPreferencePage; public class PreferencesPage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { - private BooleanFieldEditor snykCodeCheckbox; - - public PreferencesPage() { - super(GRID); - } - - @Override - public void init(IWorkbench workbench) { - setPreferenceStore(io.snyk.eclipse.plugin.properties.preferences.Preferences.getInstance().getStore()); - setMessage("Snyk Preferences"); - } - - @Override - protected void createFieldEditors() { - TokenFieldEditor tokenField = new TokenFieldEditor( - io.snyk.eclipse.plugin.properties.preferences.Preferences.getInstance(), - io.snyk.eclipse.plugin.properties.preferences.Preferences.AUTH_TOKEN_KEY, "Snyk API Token:", - getFieldEditorParent()); - addField(tokenField); - addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.PATH_KEY, "Path:", - getFieldEditorParent())); - addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ENDPOINT_KEY, - "Custom Endpoint:", getFieldEditorParent())); - addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.INSECURE_KEY, - "Allow unknown certificate authorities", getFieldEditorParent())); - - addField(space()); + private BooleanFieldEditor snykCodeCheckbox; + + public PreferencesPage() { + super(GRID); + } + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(io.snyk.eclipse.plugin.properties.preferences.Preferences.getInstance().getStore()); + setMessage("Snyk Preferences"); + } + + @Override + protected void createFieldEditors() { + TokenFieldEditor tokenField = new TokenFieldEditor( + io.snyk.eclipse.plugin.properties.preferences.Preferences.getInstance(), + io.snyk.eclipse.plugin.properties.preferences.Preferences.AUTH_TOKEN_KEY, "Snyk API Token:", + getFieldEditorParent()); + addField(tokenField); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.PATH_KEY, "Path:", + getFieldEditorParent())); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ENDPOINT_KEY, + "Custom Endpoint:", getFieldEditorParent())); + addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.INSECURE_KEY, + "Allow unknown certificate authorities", getFieldEditorParent())); + + addField(space()); addField(new LabelFieldEditor("The following options involve the Snyk Language Server.", getFieldEditorParent())); - addField(new LabelFieldEditor( - "Activating Snyk Code will cause upload of source code to Snyk or the given endpoint address.", - getFieldEditorParent())); - addField(space()); + addField(new LabelFieldEditor( + "Activating Snyk Code will cause upload of source code to Snyk or the given endpoint address.", + getFieldEditorParent())); + addField(space()); addField(new BooleanFieldEditor( io.snyk.eclipse.plugin.properties.preferences.Preferences.ACTIVATE_SNYK_OPEN_SOURCE, - "Snyk Open Source enabled", getFieldEditorParent())); - snykCodeCheckbox = new BooleanFieldEditor( - io.snyk.eclipse.plugin.properties.preferences.Preferences.ACTIVATE_SNYK_CODE, "Snyk Code enable" + "d", - getFieldEditorParent()); - - addField(snykCodeCheckbox); - addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ACTIVATE_SNYK_IAC, - "Snyk Infrastructure-as-Code enabled", getFieldEditorParent())); - - addField(space()); - addField(new LabelFieldEditor("Advanced options:", getFieldEditorParent())); - addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ORGANIZATION_KEY, - "Organization:", getFieldEditorParent())); - addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ADDITIONAL_PARAMETERS, - "Additional Parameters:", getFieldEditorParent())); - addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ADDITIONAL_ENVIRONMENT, - "Additional Environment:", getFieldEditorParent())); - - addField(space()); - BooleanFieldEditor manageBinaries = new BooleanFieldEditor(Preferences.MANAGE_BINARIES_AUTOMATICALLY, - "Update and install Snyk binaries automatically", getFieldEditorParent()); - manageBinaries.setPropertyChangeListener((PropertyChangeEvent propertyChangeEvent) -> { - System.out.println("managed bionaries changed"); - }); - addField(manageBinaries); - addField(new FileFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.LS_BINARY_KEY, - "Snyk Language Server:", getFieldEditorParent())); - addField(new FileFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.CLI_PATH, "Snyk CLI:", - getFieldEditorParent())); - - addField(space()); - - addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.SEND_ERROR_REPORTS, - "Send error reports to Snyk", getFieldEditorParent())); + "Snyk Open Source enabled", getFieldEditorParent())); + snykCodeCheckbox = new BooleanFieldEditor( + io.snyk.eclipse.plugin.properties.preferences.Preferences.ACTIVATE_SNYK_CODE, "Snyk Code enable" + "d", + getFieldEditorParent()); + + addField(snykCodeCheckbox); + addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ACTIVATE_SNYK_IAC, + "Snyk Infrastructure-as-Code enabled", getFieldEditorParent())); + + addField(space()); + addField(new LabelFieldEditor("Advanced options:", getFieldEditorParent())); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ORGANIZATION_KEY, + "Organization:", getFieldEditorParent())); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ADDITIONAL_PARAMETERS, + "Additional Parameters:", getFieldEditorParent())); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.ADDITIONAL_ENVIRONMENT, + "Additional Environment:", getFieldEditorParent())); + + addField(space()); + BooleanFieldEditor manageBinaries = new BooleanFieldEditor(Preferences.MANAGE_BINARIES_AUTOMATICALLY, + "Update and install Snyk binaries automatically", getFieldEditorParent()); + manageBinaries.setPropertyChangeListener((PropertyChangeEvent propertyChangeEvent) -> { + System.out.println("managed bionaries changed"); + }); + addField(manageBinaries); + addField(new FileFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.LS_BINARY_KEY, + "Snyk Language Server:", getFieldEditorParent())); + addField(new FileFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.CLI_PATH, "Snyk CLI:", + getFieldEditorParent())); + + addField(space()); + + addField(new BooleanFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.SEND_ERROR_REPORTS, + "Send error reports to Snyk", getFieldEditorParent())); addField(new BooleanFieldEditor(Preferences.ENABLE_TELEMETRY, "Send usage statistics to Snyk", getFieldEditorParent())); - disableSnykCodeIfOrgDisabled(); - } - - private FieldEditor space() { - return new LabelFieldEditor("", getFieldEditorParent()); - } - - @Override - public boolean performOk() { - boolean superOK = super.performOk(); - var snykView = SnykStartup.getSnykView(); - snykView.disableRunAbortActions(); - snykView.toggleRunActionEnablement(); - disableSnykCodeIfOrgDisabled(); - - new LsConfigurationUpdater().configurationChanged(); - return superOK; - } - - private void disableSnykCodeIfOrgDisabled() { - var apiClient = new ApiClient(); - if (snykCodeCheckbox.getBooleanValue() && !apiClient.checkSnykCodeEnablement()) { - String message = "Snyk Code disabled, because it is not enabled for your organization. After you close this preference page, it will stay disabled."; - snykCodeCheckbox.setLabelText(snykCodeCheckbox.getLabelText()+" ("+message+")"); - SnykLogger.logInfo(message); - } - } + + addField(space()); + + addField(new LabelFieldEditor( + "Only trusted paths are scanned by Snyk. The Trusted Folders setting allows to specify, which \n" + + "paths are safe to scan. Every path below a given path is considered safe to scan. \n" + + "Please separate entries with \"" + File.pathSeparator + "\".", + getFieldEditorParent())); + addField(new StringFieldEditor(io.snyk.eclipse.plugin.properties.preferences.Preferences.TRUSTED_FOLDERS, + "Trusted Folders:", getFieldEditorParent())); + + disableSnykCodeIfOrgDisabled(); + } + + private FieldEditor space() { + return new LabelFieldEditor("", getFieldEditorParent()); + } + + @Override + public boolean performOk() { + boolean superOK = super.performOk(); + var snykView = SnykStartup.getSnykView(); + snykView.disableRunAbortActions(); + snykView.toggleRunActionEnablement(); + disableSnykCodeIfOrgDisabled(); + + new LsConfigurationUpdater().configurationChanged(); + return superOK; + } + + private void disableSnykCodeIfOrgDisabled() { + var apiClient = new ApiClient(); + if (snykCodeCheckbox.getBooleanValue() && !apiClient.checkSnykCodeEnablement()) { + String message = "Snyk Code disabled, because it is not enabled for your organization. After you close this preference page, it will stay disabled."; + snykCodeCheckbox.setLabelText(snykCodeCheckbox.getLabelText() + " (" + message + ")"); + SnykLogger.logInfo(message); + } + } } diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/Preferences.java b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/Preferences.java index 341ef15..ff14c1d 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/Preferences.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/Preferences.java @@ -26,6 +26,7 @@ public static synchronized Preferences getInstance(PreferenceStore store) { return preferences; } + public static final String TRUSTED_FOLDERS = "trustedFolders"; public static final String AUTH_TOKEN_KEY = "authtoken"; public static final String PATH_KEY = "path"; public static final String ENDPOINT_KEY = "endpoint"; diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/SecurePreferenceStore.java b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/SecurePreferenceStore.java index 724600f..7839f63 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/SecurePreferenceStore.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/properties/preferences/SecurePreferenceStore.java @@ -4,7 +4,12 @@ import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.eclipse.equinox.security.storage.StorageException; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.preferences.ScopedPreferenceStore; public class SecurePreferenceStore extends ScopedPreferenceStore implements PreferenceStore { @@ -14,7 +19,21 @@ public class SecurePreferenceStore extends ScopedPreferenceStore implements Pref public SecurePreferenceStore() { super(InstanceScope.INSTANCE, QUALIFIER); - node = SecurePreferencesFactory.getDefault().node(QUALIFIER); + ISecurePreferences secureStorage = SecurePreferencesFactory.getDefault(); + if (secureStorage == null) { + PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { + Display display = PlatformUI.getWorkbench().getDisplay(); + Shell activeShell = display.getActiveShell(); + String message = "Eclipse was unable to create or access the Secure Storage mechanism. " + + "Please check your Secure Storage in Eclipse preferences under " + + "General -> Security -> Secure Storage. " + + "The Snyk plugin will not be able to work reliably and save preferences " + + "or the authentication token until Secure Storage can be used."; + String title = "Error accessing Eclipse Secure Storage (Snyk)"; + MessageDialog.openError(activeShell, title, message); + }); + } + node = secureStorage.node(QUALIFIER); } @Override diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/runner/SnykCliRunner.java b/plugin/src/main/java/io/snyk/eclipse/plugin/runner/SnykCliRunner.java index a1ed528..df05e48 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/runner/SnykCliRunner.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/runner/SnykCliRunner.java @@ -17,8 +17,6 @@ public class SnykCliRunner { private static final String TEST_PARAMS = "test"; private static final String FILE_PARAM = "--file="; - private static final String INSECURE = "--insecure"; - private static final String MONITOR_PARAM = "monitor"; // private static final String AUTH_PARAM = "auth"; @@ -59,12 +57,20 @@ private ProcessResult snykRun(List arguments) { private ProcessResult snykRun(List arguments, Optional navigatePath) { try { + checkIfTrusted(navigatePath.get()); ProcessBuilder processBuilder = createProcessBuilderByOS(arguments, Preferences.getInstance().getPath()); return processRunner.run(processBuilder, navigatePath); } catch (Exception e) { return ProcessResult.error(e.getMessage()); } } + + private void checkIfTrusted(File file) { + var trustedPaths = Preferences.getInstance().getPref(Preferences.TRUSTED_FOLDERS, ""); + if (!trustedPaths.contains(file.getAbsolutePath())) { + throw new UntrustedScanRequestedException(file.getAbsolutePath() + " is not trusted."); + } + } private ProcessBuilder createProcessBuilderByOS(List params, Optional path) throws Exception { ProcessBuilder processbuilder; diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/runner/UntrustedScanRequestedException.java b/plugin/src/main/java/io/snyk/eclipse/plugin/runner/UntrustedScanRequestedException.java new file mode 100644 index 0000000..2bc9afb --- /dev/null +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/runner/UntrustedScanRequestedException.java @@ -0,0 +1,27 @@ +package io.snyk.eclipse.plugin.runner; + +public class UntrustedScanRequestedException extends RuntimeException { + + private static final long serialVersionUID = 4849361078384083852L; + + public UntrustedScanRequestedException() { + } + + public UntrustedScanRequestedException(String message) { + super(message); + } + + public UntrustedScanRequestedException(Throwable cause) { + super(cause); + } + + public UntrustedScanRequestedException(String message, Throwable cause) { + super(message, cause); + } + + public UntrustedScanRequestedException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SWTWidgetHelper.java b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SWTWidgetHelper.java new file mode 100644 index 0000000..97b6bb4 --- /dev/null +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SWTWidgetHelper.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2005-2008 VecTrace (Zingo Andersen) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * bastian implementation + *******************************************************************************/ + + +package io.snyk.eclipse.plugin.wizards; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; + +/** + * @author bastian + * https://foss.heptapod.net/mercurial/mercurialeclipse/-/blob/branch/default/plugin/src/com/vectrace/MercurialEclipse/ui/SWTWidgetHelper.java + */ +public final class SWTWidgetHelper { + public static final int LABEL_WIDTH_HINT = 400; + public static final int LABEL_INDENT_WIDTH = 32; + public static final int LIST_HEIGHT_HINT = 100; + public static final int SPACER_HEIGHT = 8; + + private SWTWidgetHelper() { + // hide constructor of utility class. + } + + /** + * Creates a group that has and spans the given number of columns in its parent and which has the + * given style. + * + * @param parent + * the parent control. + * @param text + * the title of the group. + * @param span + * the number of columns (in the parent's layout) that this group will span, which is also the number of + * columns that this group has. + * @param style + * the chosen style of the grid layout for this group. + * @return a new group + */ + public static Group createGroup(Composite parent, String text, int span, int style) { + Group group = new Group(parent, SWT.NULL); + group.setText(text); + GridData data = new GridData(style); + data.horizontalSpan = span; + // data.widthHint = GROUP_WIDTH; + + group.setLayoutData(data); + GridLayout layout = new GridLayout(); + layout.numColumns = span; + group.setLayout(layout); + return group; + } + + /** + * Creates a group that spans two columns. + * + * @param parent + * the parent control + * @param text + * the title of the group + * @param style + * the chosen style for this group + * @return a new group + */ + public static Group createGroup(Composite parent, String text, int style) { + return createGroup(parent, text, 2, style); + } + + /** + * Creates a group that has two columns and which style is horizontal fill. + * + * @param parent + * the parent control + * @param text + * the title of the group + * @return a new group + */ + public static Group createGroup(Composite parent, String text) { + return createGroup(parent, text, GridData.FILL_HORIZONTAL); + } +} diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizard.java b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizard.java index c8ae8cd..80cda0f 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizard.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizard.java @@ -11,9 +11,9 @@ public class SnykWizard extends Wizard implements INewWizard { protected SnykWizardConfigureAPIPage configureAPIPage; protected SnykWizardAuthenticatePage authenticatePage; - + protected SnykWizardModel model; - + protected IWorkbench workbench; protected IStructuredSelection selection; @@ -22,18 +22,18 @@ public SnykWizard() { model = new SnykWizardModel(); setNeedsProgressMonitor(true); } - + @Override public String getWindowTitle() { return "Snyk Wizard"; } - + @Override public void addPages() { - configureAPIPage = new SnykWizardConfigureAPIPage(); + configureAPIPage = new SnykWizardConfigureAPIPage(); addPage(configureAPIPage); - - authenticatePage = new SnykWizardAuthenticatePage(); + + authenticatePage = new SnykWizardAuthenticatePage(); addPage(authenticatePage); } @@ -41,22 +41,23 @@ public void init(IWorkbench workbench, IStructuredSelection selection) { this.workbench = workbench; this.selection = selection; } - + public boolean canFinish() { if (this.getContainer().getCurrentPage() == authenticatePage) { return true; } return false; } - + public boolean performCancel() { model.resetPreferences(); return true; } - public boolean performFinish() { + public boolean performFinish() { new LsConfigurationUpdater().configurationChanged(); SnykExtendedLanguageClient.getInstance().triggerAuthentication(); + SnykExtendedLanguageClient.getInstance().trustWorkspaceFolders(); return true; } } diff --git a/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizardAuthenticatePage.java b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizardAuthenticatePage.java index c0a7bf6..f4e12c3 100644 --- a/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizardAuthenticatePage.java +++ b/plugin/src/main/java/io/snyk/eclipse/plugin/wizards/SnykWizardAuthenticatePage.java @@ -3,21 +3,27 @@ import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Text; import io.snyk.eclipse.plugin.properties.preferences.Preferences; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Listener; public class SnykWizardAuthenticatePage extends WizardPage implements Listener { private Text endpoint; private Button unknownCerts; + private String trustMessage = "⚠️ When scanning folder files, Snyk may automatically execute code such as invoking the package manager to get dependency information. " + + "You should only scan projects you trust. More Info" + + "\n\nOn finishing the wizard, the plugin will open a browser to authenticate you, trust the current workspace projects and trigger a scan."; public SnykWizardAuthenticatePage() { super("Snyk Wizard"); @@ -27,37 +33,45 @@ public SnykWizardAuthenticatePage() { @Override public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - GridLayout gl = new GridLayout(); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); + Composite composite = new Composite(parent, SWT.NONE); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); - int ncol = 2; - gl.numColumns = ncol; - composite.setLayout(gl); + GridLayout gl = new GridLayout(); + int ncol = 2; + gl.numColumns = ncol; + composite.setLayout(gl); - Label endpointLabel = new Label(composite, SWT.NONE); - endpointLabel.setText("Endpoint:"); + Group endpointGroup = SWTWidgetHelper.createGroup(composite, ""); - endpoint = new Text(composite, SWT.BORDER | SWT.READ_ONLY); - endpoint.setLayoutData(gd); - - createLine(composite, ncol); + Label endpointLabel = new Label(endpointGroup, SWT.NONE); + endpointLabel.setText("Endpoint:"); + endpoint = new Text(endpointGroup, SWT.BORDER | SWT.READ_ONLY); + endpoint.setLayoutData(gd); - Label unknownCertsLabel = new Label(composite, SWT.NONE); - unknownCertsLabel.setText("Allow unknown certificate authorities:"); + Label unknownCertsLabel = new Label(endpointGroup, SWT.NONE); + unknownCertsLabel.setText("Allow unknown certificate authorities:"); - unknownCerts = new Button(composite, SWT.CHECK); - unknownCerts.setLayoutData(gd); + unknownCerts = new Button(endpointGroup, SWT.CHECK); + unknownCerts.setLayoutData(gd); - // required to avoid an error in the system - setControl(composite); - setPageComplete(false); + Group trustGroup = SWTWidgetHelper.createGroup(composite, ""); + + Link trustText = new Link(trustGroup, SWT.NONE); + trustText.setText(trustMessage); + gd = new GridData(GridData.FILL_BOTH); + trustText.setLayoutData(gd); + trustText.setBackground(new Color(0, 0, 0, 0)); + trustText.addListener(SWT.Selection, event -> org.eclipse.swt.program.Program.launch(event.text)); + + // required to avoid an error in the system + setControl(composite); + setPageComplete(false); } public void handleEvent(Event e) { getWizard().getContainer().updateButtons(); } - + public boolean isPageComplete() { return true; } @@ -66,11 +80,4 @@ void onEnterPage() { endpoint.setText(Preferences.getInstance().getEndpoint()); unknownCerts.setSelection(Preferences.getInstance().getBooleanPref(Preferences.INSECURE_KEY)); } - - private void createLine(Composite parent, int ncol) { - Label line = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.BOLD); - GridData gridData = new GridData(GridData.FILL_HORIZONTAL); - gridData.horizontalSpan = ncol; - line.setLayoutData(gridData); - } } diff --git a/plugin/src/main/java/io/snyk/languageserver/LsConfigurationUpdater.java b/plugin/src/main/java/io/snyk/languageserver/LsConfigurationUpdater.java index b1e7288..35b1871 100644 --- a/plugin/src/main/java/io/snyk/languageserver/LsConfigurationUpdater.java +++ b/plugin/src/main/java/io/snyk/languageserver/LsConfigurationUpdater.java @@ -4,6 +4,7 @@ import io.snyk.eclipse.plugin.properties.preferences.Preferences; import io.snyk.eclipse.plugin.utils.SnykLogger; +import java.io.File; import java.util.Collections; import org.eclipse.core.resources.IProject; @@ -45,23 +46,30 @@ public void configurationChanged() { Settings getCurrentSettings() { Preferences preferences = Preferences.getInstance(); - String activateSnykOpenSource = preferences.getPref(Preferences.ACTIVATE_SNYK_OPEN_SOURCE, "true"); - String activateSnykCode = preferences.getPref(Preferences.ACTIVATE_SNYK_CODE, "false"); - String activateSnykIac = preferences.getPref(Preferences.ACTIVATE_SNYK_IAC, "true"); - String insecure = preferences.getPref(Preferences.INSECURE_KEY, "false"); + String activateSnykOpenSource = preferences.getPref(Preferences.ACTIVATE_SNYK_OPEN_SOURCE, + Boolean.TRUE.toString()); + String activateSnykCode = preferences.getPref(Preferences.ACTIVATE_SNYK_CODE, Boolean.FALSE.toString()); + String activateSnykIac = preferences.getPref(Preferences.ACTIVATE_SNYK_IAC, Boolean.TRUE.toString()); + String insecure = preferences.getPref(Preferences.INSECURE_KEY, Boolean.FALSE.toString()); String endpoint = preferences.getPref(Preferences.ENDPOINT_KEY, ""); String additionalParams = preferences.getPref(Preferences.ADDITIONAL_PARAMETERS, ""); String additionalEnv = preferences.getPref(Preferences.ADDITIONAL_ENVIRONMENT, ""); String path = preferences.getPref(Preferences.PATH_KEY, ""); String sendErrorReports = preferences.getPref(Preferences.SEND_ERROR_REPORTS, ""); - String enableTelemetry = preferences.getPref(Preferences.ENABLE_TELEMETRY, "false"); + String enableTelemetry = preferences.getPref(Preferences.ENABLE_TELEMETRY, Boolean.FALSE.toString()); String organization = preferences.getPref(Preferences.ORGANIZATION_KEY, ""); - String manageBinariesAutomatically = preferences.getPref(Preferences.MANAGE_BINARIES_AUTOMATICALLY, "true"); + String manageBinariesAutomatically = preferences.getPref(Preferences.MANAGE_BINARIES_AUTOMATICALLY, Boolean.TRUE.toString()); String cliPath = preferences.getPref(Preferences.CLI_PATH, ""); String token = preferences.getPref(Preferences.AUTH_TOKEN_KEY, ""); String integrationName = Activator.INTEGRATION_NAME; String integrationVersion = Activator.PLUGIN_VERSION; String automaticAuthentication = "false"; + String trustedFoldersString = preferences.getPref(Preferences.TRUSTED_FOLDERS); + String[] trustedFolders = new String[0]; + if (trustedFoldersString != null && !trustedFoldersString.isBlank()) { + trustedFolders = trustedFoldersString.split(File.pathSeparator); + } + String enableTrustedFolderFeature = Boolean.TRUE.toString(); return new Settings(activateSnykOpenSource, activateSnykCode, activateSnykIac, @@ -78,7 +86,9 @@ Settings getCurrentSettings() { token, integrationName, integrationVersion, - automaticAuthentication + automaticAuthentication, + trustedFolders, + enableTrustedFolderFeature ); } @@ -101,6 +111,8 @@ static class Settings { private final String integrationName; private final String integrationVersion; private final String automaticAuthentication; + private final String[] trustedFolders; + private final String enableTrustedFoldersFeature; public Settings(String activateSnykOpenSource, String activateSnykCode, @@ -118,7 +130,9 @@ public Settings(String activateSnykOpenSource, String token, String integrationName, String integrationVersion, - String automaticAuthentication + String automaticAuthentication, + String[] trustedFolders, + String enableTrustedFoldersFeature ) { this.activateSnykOpenSource = activateSnykOpenSource; this.activateSnykCode = activateSnykCode; @@ -137,6 +151,8 @@ public Settings(String activateSnykOpenSource, this.integrationName = integrationName; this.integrationVersion = integrationVersion; this.automaticAuthentication = automaticAuthentication; + this.trustedFolders = trustedFolders; + this.enableTrustedFoldersFeature = enableTrustedFoldersFeature; } public String getPath() { @@ -202,9 +218,17 @@ public String getIntegrationName() { public String getIntegrationVersion() { return integrationVersion; } - + public String getAutomaticAuthentication() { return automaticAuthentication; } + + public String[] getTrustedFolders() { + return trustedFolders; + } + + public String getEnableTrustedFoldersFeature() { + return enableTrustedFoldersFeature; + } } } diff --git a/plugin/src/main/java/io/snyk/languageserver/download/LsBinaries.java b/plugin/src/main/java/io/snyk/languageserver/download/LsBinaries.java index 86b9d22..f097115 100644 --- a/plugin/src/main/java/io/snyk/languageserver/download/LsBinaries.java +++ b/plugin/src/main/java/io/snyk/languageserver/download/LsBinaries.java @@ -4,7 +4,7 @@ public class LsBinaries { private static final String LS_DOWNLOAD_BASE_URL = "https://static.snyk.io/snyk-ls"; - public static final String REQUIRED_LS_PROTOCOL_VERSION = "3"; + public static final String REQUIRED_LS_PROTOCOL_VERSION = "4"; public static URI getBaseUri() { return URI.create(String.format("%s/%s", LS_DOWNLOAD_BASE_URL, REQUIRED_LS_PROTOCOL_VERSION)); diff --git a/plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java b/plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java index bb728b6..119d9c6 100644 --- a/plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java +++ b/plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java @@ -1,7 +1,12 @@ package io.snyk.languageserver.protocolextension; +import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import org.eclipse.core.resources.IProject; import org.eclipse.jdt.internal.core.JavaProject; @@ -19,6 +24,7 @@ import org.eclipse.lsp4j.ShowDocumentResult; import org.eclipse.lsp4j.WorkDoneProgressCreateParams; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; import org.eclipse.ui.ISelectionService; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; @@ -30,6 +36,7 @@ import io.snyk.eclipse.plugin.wizards.SnykWizard; import io.snyk.languageserver.protocolextension.messageObjects.HasAuthenticatedParam; import io.snyk.languageserver.protocolextension.messageObjects.SnykIsAvailableCliParams; +import io.snyk.languageserver.protocolextension.messageObjects.SnykTrustedFoldersParams; @SuppressWarnings("restriction") public class SnykExtendedLanguageClient extends LanguageClientImpl { @@ -50,9 +57,8 @@ public void triggerScan(IWorkbenchWindow window) { if (Preferences.getInstance().getAuthToken().isBlank()) { runSnykWizard(); } else { - ExecuteCommandParams params = new ExecuteCommandParams("snyk.workspace.scan", new ArrayList<>()); try { - getLanguageServer().getWorkspaceService().executeCommand(params); + executeCommand("snyk.workspace.scan", new ArrayList<>()); if (window == null) { window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); @@ -82,12 +88,11 @@ public void triggerScan(IWorkbenchWindow window) { } public void triggerAuthentication() { - ExecuteCommandParams params = new ExecuteCommandParams("snyk.login", new ArrayList<>()); - try { - getLanguageServer().getWorkspaceService().executeCommand(params); - } catch (Exception e) { - SnykLogger.logError(e); - } + executeCommand("snyk.login", new ArrayList<>()); + } + + public void trustWorkspaceFolders() { + executeCommand("snyk.trustWorkspaceFolders", new ArrayList<>()); } @JsonNotification(value = "$/snyk.hasAuthenticated") @@ -106,6 +111,21 @@ public void isAvailableCli(SnykIsAvailableCliParams param) { enableSnykViewRunActions(); } + @JsonNotification(value = "$/snyk.addTrustedFolders") + public void addTrustedPaths(SnykTrustedFoldersParams param) { + var prefs = Preferences.getInstance(); + var storedTrustedPaths = prefs.getPref(Preferences.TRUSTED_FOLDERS, ""); + var trustedPaths = storedTrustedPaths.split(File.pathSeparator); + var pathSet = new HashSet<>(Arrays.asList(trustedPaths)); + pathSet.addAll(Arrays.asList(param.getTrustedFolders())); + Preferences.getInstance().store(Preferences.TRUSTED_FOLDERS, + pathSet.stream() + .filter(s -> !s.isBlank()) + .map(s -> s.trim()) + .distinct() + .collect(Collectors.joining(File.pathSeparator))); + } + @Override public CompletableFuture createProgress(WorkDoneProgressCreateParams params) { return progressMgr.createProgress(params); @@ -147,6 +167,15 @@ private void runForProject(String projectName) { } } + private void executeCommand(@NonNull String command, List arguments) { + ExecuteCommandParams params = new ExecuteCommandParams(command, arguments); + try { + getLanguageServer().getWorkspaceService().executeCommand(params); + } catch (Exception e) { + SnykLogger.logError(e); + } + } + // TODO: remove once LSP4e supports `showDocument` in its next release (it's // been merged to it already) @Override @@ -163,4 +192,7 @@ public CompletableFuture showDocument(ShowDocumentParams par return new ShowDocumentResult(true); }); } + + + } diff --git a/plugin/src/main/java/io/snyk/languageserver/protocolextension/messageObjects/SnykTrustedFoldersParams.java b/plugin/src/main/java/io/snyk/languageserver/protocolextension/messageObjects/SnykTrustedFoldersParams.java new file mode 100644 index 0000000..b700159 --- /dev/null +++ b/plugin/src/main/java/io/snyk/languageserver/protocolextension/messageObjects/SnykTrustedFoldersParams.java @@ -0,0 +1,13 @@ +package io.snyk.languageserver.protocolextension.messageObjects; + +public class SnykTrustedFoldersParams { + private String[] trustedFolders; + + public String[] getTrustedFolders() { + return trustedFolders; + } + + public void setTrustedFolders(String[] trustedFolders) { + this.trustedFolders = trustedFolders; + } +} diff --git a/target-platform/target-platform.target b/target-platform/target-platform.target index e115f9d..01e61fb 100644 --- a/target-platform/target-platform.target +++ b/target-platform/target-platform.target @@ -43,7 +43,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4 + 2.13.4.2 jar diff --git a/tests/pom.xml b/tests/pom.xml index 6804377..836cf5e 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -25,7 +25,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4 + 2.13.4.2 jar diff --git a/tests/src/test/java/io/snyk/eclipse/plugin/runner/SnykCliRunnerTest.java b/tests/src/test/java/io/snyk/eclipse/plugin/runner/SnykCliRunnerTest.java new file mode 100644 index 0000000..f07b6a9 --- /dev/null +++ b/tests/src/test/java/io/snyk/eclipse/plugin/runner/SnykCliRunnerTest.java @@ -0,0 +1,38 @@ +package io.snyk.eclipse.plugin.runner; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +import io.snyk.eclipse.plugin.properties.preferences.InMemoryPreferenceStore; +import io.snyk.eclipse.plugin.properties.preferences.Preferences; +import io.snyk.eclipse.plugin.properties.preferences.PreferencesUtils; + +class SnykCliRunnerTest { + + @Test + void testRunDoesntAllowScansOfUntrustedPath() { + PreferencesUtils.setPreferences(Preferences.getInstance(new InMemoryPreferenceStore())); + SnykCliRunner cut = new SnykCliRunner(); + File navigatePath = new File("untrusted/path"); + + ProcessResult result = cut.snykTest(navigatePath); + + assertTrue(result.getError().endsWith(navigatePath.getAbsolutePath() + " is not trusted.")); + } + + @Test + void testRunAllowsScanOfTrustedPath() { + File navigatePath = new File("trusted/path"); + InMemoryPreferenceStore store = new InMemoryPreferenceStore(); + store.put(Preferences.TRUSTED_FOLDERS, "a" + File.pathSeparator + navigatePath.getAbsolutePath() + File.pathSeparator + "b"); + PreferencesUtils.setPreferences(Preferences.getInstance(store)); + + SnykCliRunner cut = new SnykCliRunner(); + ProcessResult result = cut.snykTest(navigatePath); + + assertFalse(result.getError().endsWith(navigatePath.getAbsolutePath() + " is not trusted.")); + } +} diff --git a/tests/src/test/java/io/snyk/languageserver/SnykLanguageServerTest.java b/tests/src/test/java/io/snyk/languageserver/SnykLanguageServerTest.java index 04bcce7..2ba746c 100644 --- a/tests/src/test/java/io/snyk/languageserver/SnykLanguageServerTest.java +++ b/tests/src/test/java/io/snyk/languageserver/SnykLanguageServerTest.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.io.File; + class SnykLanguageServerTest { @Test @@ -18,4 +20,34 @@ void getInitializationOptions() { assertInstanceOf(LsConfigurationUpdater.Settings.class, output); } + + @Test + void getInitializationOptionsContainsTrustedPaths() { + InMemoryPreferenceStore store = new InMemoryPreferenceStore(); + String trustedPaths = "a" + File.pathSeparatorChar + "b/c"; + store.put(Preferences.TRUSTED_FOLDERS, trustedPaths); + PreferencesUtils.setPreferences(Preferences.getInstance(store)); + SnykLanguageServer snykStreamConnectionProvider = new SnykLanguageServer(); + + Object output = snykStreamConnectionProvider.getInitializationOptions(null); + + assertInstanceOf(LsConfigurationUpdater.Settings.class, output); + LsConfigurationUpdater.Settings settings = (LsConfigurationUpdater.Settings) output; + assertEquals("a", settings.getTrustedFolders()[0]); + assertEquals("b/c", settings.getTrustedFolders()[1]); + } + + @Test + void getInitializationOptionsDoesNotContainsTrustedPathsIfNoneKnown() { + InMemoryPreferenceStore store = new InMemoryPreferenceStore(); + PreferencesUtils.setPreferences(Preferences.getInstance(store)); + SnykLanguageServer snykStreamConnectionProvider = new SnykLanguageServer(); + + Object output = snykStreamConnectionProvider.getInitializationOptions(null); + + assertInstanceOf(LsConfigurationUpdater.Settings.class, output); + LsConfigurationUpdater.Settings settings = (LsConfigurationUpdater.Settings) output; + assertNotNull(settings.getTrustedFolders()); + assertEquals(0, settings.getTrustedFolders().length); + } } diff --git a/tests/src/test/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClientTest.java b/tests/src/test/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClientTest.java new file mode 100644 index 0000000..2c89899 --- /dev/null +++ b/tests/src/test/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClientTest.java @@ -0,0 +1,46 @@ +package io.snyk.languageserver.protocolextension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.snyk.eclipse.plugin.properties.preferences.InMemoryPreferenceStore; +import io.snyk.eclipse.plugin.properties.preferences.Preferences; +import io.snyk.eclipse.plugin.properties.preferences.PreferencesUtils; +import io.snyk.languageserver.protocolextension.messageObjects.HasAuthenticatedParam; +import io.snyk.languageserver.protocolextension.messageObjects.SnykIsAvailableCliParams; +import io.snyk.languageserver.protocolextension.messageObjects.SnykTrustedFoldersParams; + +class SnykExtendedLanguageClientTest { + private InMemoryPreferenceStore store = new InMemoryPreferenceStore(); + private SnykExtendedLanguageClient cut = new SnykExtendedLanguageClient(); + + @BeforeEach + void setUp() { + store = new InMemoryPreferenceStore(); + PreferencesUtils.setPreferences(Preferences.getInstance(store)); + } + + @Test + void testAddTrustedPathsAddsPathToPreferenceStore() { + SnykTrustedFoldersParams param = new SnykTrustedFoldersParams(); + param.setTrustedFolders(new String[] {"trusted/path "}); + + cut.addTrustedPaths(param); + + assertEquals("trusted/path", store.getString(Preferences.TRUSTED_FOLDERS, "")); + } + + @Test + void testAddTrustedPathsDeduplicatesAndTrims() { + SnykTrustedFoldersParams param = new SnykTrustedFoldersParams(); + param.setTrustedFolders(new String[] {"trusted/path", "trusted/path", " trusted/path "}); + + cut.addTrustedPaths(param); + + assertEquals("trusted/path", store.getString(Preferences.TRUSTED_FOLDERS, "")); + } + +}