From 80f8911739a89dcb9ee26184b87ec5092e22364b Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Thu, 13 Jun 2024 16:22:30 +0200 Subject: [PATCH] fix: automatically migrate old endpoint (#547) * fix: automatically migrate old endpoint * docs: update CHANGELOG.md --- CHANGELOG.md | 4 ++ src/main/kotlin/io/snyk/plugin/Utils.kt | 7 ++++ .../io/snyk/plugin/ui/SnykSettingsDialog.kt | 19 +++++---- .../kotlin/snyk/common/CustomEndpoints.kt | 9 +++- src/test/kotlin/io/snyk/plugin/UtilsKtTest.kt | 6 +++ .../plugin/cli/ConsoleCommandRunnerTest.kt | 2 +- .../kotlin/snyk/common/CustomEndpointsTest.kt | 42 +++++++++++++++---- 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81531bd21..d89535237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Snyk Security Changelog +## [2.8.6] +### Fixed +- automatically migrate old-format endpoint to https://api.xxx.snyk.io endpoint and save it in settings. Also, add tooltip to custom endpoint field explaining the format. + ## [2.8.5] ### Fixed - don't display balloon warnings if IaC error is ignored (e.g. no IaC files found) diff --git a/src/main/kotlin/io/snyk/plugin/Utils.kt b/src/main/kotlin/io/snyk/plugin/Utils.kt index aaac7cdd9..417173e31 100644 --- a/src/main/kotlin/io/snyk/plugin/Utils.kt +++ b/src/main/kotlin/io/snyk/plugin/Utils.kt @@ -169,6 +169,13 @@ fun getSyncPublisher(project: Project, topic: Topic): L? { val List.head: T get() = first() +fun isAdditionalParametersValid(params: String?): Boolean { + params.isNullOrEmpty() && return true + + val list = params!!.split(" ") + return !list.contains("-d") +} + fun isUrlValid(url: String?): Boolean { url.isNullOrEmpty() && return true diff --git a/src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt b/src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt index 98979a5d7..54e9826a5 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt @@ -32,6 +32,7 @@ import io.snyk.plugin.events.SnykCliDownloadListener import io.snyk.plugin.getCliFile import io.snyk.plugin.getSnykCliAuthenticationService import io.snyk.plugin.getSnykCliDownloaderService +import io.snyk.plugin.isAdditionalParametersValid import io.snyk.plugin.isProjectSettingsAvailable import io.snyk.plugin.isUrlValid import io.snyk.plugin.pluginSettings @@ -77,12 +78,12 @@ class SnykSettingsDialog( private val tokenTextField = JBPasswordField() private val receiveTokenButton = JButton("Connect IDE to Snyk") private val customEndpointTextField = JTextField() - private val organizationTextField: JTextField = JTextField() - private val ignoreUnknownCACheckBox: JCheckBox = JCheckBox() - private val usageAnalyticsCheckBox: JCheckBox = JCheckBox() - private val crashReportingCheckBox = JCheckBox() - private val scanOnSaveCheckbox = JCheckBox() - private val additionalParametersTextField: JTextField = ExpandableTextField() + private val organizationTextField: JTextField = JTextField().apply { toolTipText = "The UUID of your organization or the org stub" } + private val ignoreUnknownCACheckBox: JCheckBox = JCheckBox().apply { toolTipText = "Enabling this causes SSL certificate validation to be disabled" } + private val usageAnalyticsCheckBox: JCheckBox = JCheckBox().apply { toolTipText = "If enabled, send analytics to Amplitude" } + private val crashReportingCheckBox = JCheckBox().apply { toolTipText = "If enabled, send error reports to Sentry" } + private val scanOnSaveCheckbox = JCheckBox().apply { toolTipText = "If enabled, automatically scan on save, start-up and configuration change" } + private val additionalParametersTextField: JTextField = ExpandableTextField().apply { toolTipText = "--all-projects is already defaulted, -d causes problems" } private val scanTypesPanelOuter = ScanTypesPanel(project, rootPanel) private val codeAlertPanel = scanTypesPanelOuter.codeAlertPanel @@ -215,7 +216,10 @@ class SnykSettingsDialog( ) val customEndpointLabel = JLabel("Custom endpoint:") + val customEndpointTooltip = "The correct endpoint format is https://api.xxx.snyk[gov].io, e.g. https://api.eu.snyk.io" + customEndpointLabel.toolTipText = customEndpointTooltip customEndpointLabel.labelFor = customEndpointTextField + customEndpointTextField.toolTipText = customEndpointTooltip generalSettingsPanel.add( customEndpointLabel, baseGridConstraintsAnchorWest( @@ -642,7 +646,8 @@ class SnykSettingsDialog( private fun initializeValidation() { setupValidation(tokenTextField, "Invalid token", ::isTokenValid) - setupValidation(customEndpointTextField, "Invalid custom endpoint URL", ::isUrlValid) + setupValidation(customEndpointTextField, "Invalid custom endpoint URL, please use https://api.xxx.snyk[gov].io", ::isUrlValid) + setupValidation(additionalParametersTextField, "The -d option is not supported by the Snyk IntelliJ plugin", ::isAdditionalParametersValid) } private fun setupValidation(textField: JTextField, message: String, isValidText: (sourceStr: String?) -> Boolean) { diff --git a/src/main/kotlin/snyk/common/CustomEndpoints.kt b/src/main/kotlin/snyk/common/CustomEndpoints.kt index 44009361c..4625e807b 100644 --- a/src/main/kotlin/snyk/common/CustomEndpoints.kt +++ b/src/main/kotlin/snyk/common/CustomEndpoints.kt @@ -92,12 +92,17 @@ fun isSnykCodeAvailable(endpointUrl: String?): Boolean { */ internal fun resolveCustomEndpoint(endpointUrl: String?): String { return if (endpointUrl.isNullOrEmpty()) { - "https://api.snyk.io" + val normalizedEndpointURL = "https://api.snyk.io" + pluginSettings().customEndpointUrl = normalizedEndpointURL + normalizedEndpointURL } else { - endpointUrl + val normalizedEndpointURL = endpointUrl .removeTrailingSlashesIfPresent() .removeSuffix("/api") + .replace("https://snyk.io", "https://api.snyk.io") .replace("https://app.", "https://api.") + pluginSettings().customEndpointUrl = normalizedEndpointURL + normalizedEndpointURL } } diff --git a/src/test/kotlin/io/snyk/plugin/UtilsKtTest.kt b/src/test/kotlin/io/snyk/plugin/UtilsKtTest.kt index 9798a98c1..355f31312 100644 --- a/src/test/kotlin/io/snyk/plugin/UtilsKtTest.kt +++ b/src/test/kotlin/io/snyk/plugin/UtilsKtTest.kt @@ -74,4 +74,10 @@ class UtilsKtTest { assertFalse(virtualFile.urlContainsDriveLetter()) } + + @Test + fun isAdditionalParametersValid() { + assertFalse(isAdditionalParametersValid("-d")) + assertTrue(isAdditionalParametersValid("asdf")) + } } diff --git a/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt b/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt index 79ed47c82..beaf8ccb0 100644 --- a/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt +++ b/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt @@ -116,7 +116,7 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { val oldEndpoint = pluginSettings().customEndpointUrl try { val generalCommandLine = GeneralCommandLine("") - val expectedEndpoint = "https://snyk.io/v1" + val expectedEndpoint = "https://api.snyk.io/v1" generalCommandLine.environment["INTERNAL_OAUTH_TOKEN_STORAGE"] = "{}" assertFalse(URI(expectedEndpoint).isOauth()) diff --git a/src/test/kotlin/snyk/common/CustomEndpointsTest.kt b/src/test/kotlin/snyk/common/CustomEndpointsTest.kt index 7d36f81a8..a37278cc5 100644 --- a/src/test/kotlin/snyk/common/CustomEndpointsTest.kt +++ b/src/test/kotlin/snyk/common/CustomEndpointsTest.kt @@ -1,7 +1,6 @@ package snyk.common import io.mockk.every -import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll import io.snyk.plugin.pluginSettings @@ -15,11 +14,12 @@ import org.junit.Test import java.net.URI class CustomEndpointsTest { + lateinit var settings :SnykApplicationSettingsStateService @Before fun setUp() { unmockkAll() mockkStatic("io.snyk.plugin.UtilsKt") - val settings = mockk(relaxed = true) + settings = SnykApplicationSettingsStateService() every { pluginSettings() } returns settings } @@ -37,6 +37,32 @@ class CustomEndpointsTest { assertEquals("https://api.snyk.io", endpointForEmpty) } + @Test + fun `resolveCustomEndpoint converts app endpoint to api endpoint and saves it in settings`() { + val endpointUrl = "https://app.snyk.io/api" + val snykEndpointUrl = "https://snyk.io/api" + val expected = "https://api.snyk.io" + + settings.customEndpointUrl = endpointUrl + var actual = resolveCustomEndpoint(endpointUrl) + + assertEquals(expected, actual) + assertEquals(expected, pluginSettings().customEndpointUrl) + + settings.customEndpointUrl = snykEndpointUrl + actual = resolveCustomEndpoint(snykEndpointUrl) + + assertEquals(expected, actual) + assertEquals(expected, pluginSettings().customEndpointUrl) + + settings.customEndpointUrl = "" + actual = resolveCustomEndpoint("") + + assertEquals(expected, actual) + assertEquals(expected, pluginSettings().customEndpointUrl) + } + + @Test fun `resolveCustomEndpoint removes all trailing slashes if present`() { val endpointWithSingleTrailingSlash = resolveCustomEndpoint("https://app.on-prem.internal/api") @@ -64,8 +90,8 @@ class CustomEndpointsTest { @Test fun `isSnykCodeAvailable returns true if local engine is enabled`() { - every { pluginSettings().localCodeEngineUrl } returns "https://api.foo.bar" - every { pluginSettings().localCodeEngineEnabled } returns true + settings.localCodeEngineUrl = "https://api.foo.bar" + settings.localCodeEngineEnabled = true assertEquals(isSnykCodeAvailable("https://api.foo.bar"), true) } @@ -96,8 +122,8 @@ class CustomEndpointsTest { @Test fun `toSnykCodeApiUrl returns local engine URL if local engine is enabled`() { - every { pluginSettings().localCodeEngineUrl } returns "https://api.foo.bar" - every { pluginSettings().localCodeEngineEnabled } returns true + settings.localCodeEngineUrl = "https://api.foo.bar" + settings.localCodeEngineEnabled = true val apiUrlForProduction = toSnykCodeApiUrl("https://api.snyk.io") assertEquals("https://api.foo.bar", apiUrlForProduction) @@ -131,8 +157,8 @@ class CustomEndpointsTest { @Test fun `toSnykCodeSettingsUrl returns APP-URL for local engine`() { - every { pluginSettings().localCodeEngineUrl } returns "https://api.foo.bar" - every { pluginSettings().localCodeEngineEnabled } returns true + settings.localCodeEngineUrl = "https://api.foo.bar" + settings.localCodeEngineEnabled = true assertEquals("https://app.foo.bar", toSnykCodeSettingsUrl("https://api.foo.bar")) }