diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt index ae3f633f8..1a4b91f04 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt @@ -21,7 +21,7 @@ import snyk.analytics.QuickFixIsDisplayed import snyk.analytics.QuickFixIsTriggered import snyk.analytics.WelcomeIsViewed import snyk.common.SnykError -import snyk.common.isFedramp +import snyk.common.isAnalyticsPermitted import snyk.container.ContainerResult import snyk.iac.IacResult import snyk.oss.OssResult @@ -146,7 +146,7 @@ class SnykAnalyticsService : Disposable { } fun obtainUserId(token: String?): String { - if (!settings.usageAnalyticsEnabled || isFedramp()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) { log.warn("Token is null or empty, or analytics disabled. User public id will not be obtained.") return "" } @@ -164,7 +164,7 @@ class SnykAnalyticsService : Disposable { } fun identify() { - if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted() || userId.isBlank()) { return } @@ -174,7 +174,7 @@ class SnykAnalyticsService : Disposable { } fun logWelcomeIsViewed(event: WelcomeIsViewed) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "welcomeIsViewed") { itly.logWelcomeIsViewed(userId, event) @@ -182,7 +182,7 @@ class SnykAnalyticsService : Disposable { } fun logAnalysisIsTriggered(event: AnalysisIsTriggered) { - if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted() || userId.isBlank()) { return } @@ -192,7 +192,7 @@ class SnykAnalyticsService : Disposable { } private fun logAnalysisIsReady(event: AnalysisIsReady) { - if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted() || userId.isBlank()) { return } @@ -202,7 +202,7 @@ class SnykAnalyticsService : Disposable { } fun logIssueInTreeIsClicked(event: IssueInTreeIsClicked) { - if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted() || userId.isBlank()) { return } @@ -212,7 +212,7 @@ class SnykAnalyticsService : Disposable { } fun logHealthScoreIsClicked(event: HealthScoreIsClicked) { - if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) { + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted() || userId.isBlank()) { return } @@ -222,7 +222,7 @@ class SnykAnalyticsService : Disposable { } fun logPluginIsInstalled(event: PluginIsInstalled) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "pluginIsInstalled") { itly.logPluginIsInstalled(userId, event) @@ -230,7 +230,7 @@ class SnykAnalyticsService : Disposable { } fun logPluginIsUninstalled(event: PluginIsUninstalled) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "pluginIsUninstalled") { itly.logPluginIsUninstalled(userId, event) @@ -238,7 +238,7 @@ class SnykAnalyticsService : Disposable { } fun logAuthenticateButtonIsClicked(event: AuthenticateButtonIsClicked) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "authenticateButtonIsClicked") { itly.logAuthenticateButtonIsClicked(userId, event) @@ -246,7 +246,7 @@ class SnykAnalyticsService : Disposable { } fun logQuickFixIsDisplayed(event: QuickFixIsDisplayed) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "quickFixIsDisplayed") { itly.logQuickFixIsDisplayed(userId, event) @@ -254,7 +254,7 @@ class SnykAnalyticsService : Disposable { } fun logQuickFixIsTriggered(event: QuickFixIsTriggered) { - if (!settings.usageAnalyticsEnabled || isFedramp()) return + if (!settings.usageAnalyticsEnabled || !isAnalyticsPermitted()) return catchAll(log, "quickFixIsTriggered") { itly.logQuickFixIsTriggered(userId, event) diff --git a/src/main/kotlin/snyk/amplitude/AmplitudeExperimentService.kt b/src/main/kotlin/snyk/amplitude/AmplitudeExperimentService.kt index 8be818699..4f2de90aa 100644 --- a/src/main/kotlin/snyk/amplitude/AmplitudeExperimentService.kt +++ b/src/main/kotlin/snyk/amplitude/AmplitudeExperimentService.kt @@ -9,7 +9,7 @@ import snyk.amplitude.api.AmplitudeExperimentApiClient import snyk.amplitude.api.AmplitudeExperimentApiClient.Defaults.FALLBACK_VARIANT import snyk.amplitude.api.ExperimentUser import snyk.amplitude.api.Variant -import snyk.common.isFedramp +import snyk.common.isAnalyticsPermitted import java.io.IOException import java.util.Properties import java.util.concurrent.ConcurrentHashMap @@ -31,7 +31,7 @@ class AmplitudeExperimentService : Disposable { prop.load(javaClass.classLoader.getResourceAsStream("application.properties")) val apiKey = prop.getProperty("amplitude.experiment.api-key") ?: "" val settings = pluginSettings() - if (settings.usageAnalyticsEnabled && !isFedramp()) { + if (settings.usageAnalyticsEnabled && !isAnalyticsPermitted()) { apiClient = AmplitudeExperimentApiClient.create(apiKey = apiKey) } else { LOG.debug("Amplitude experiment disabled.") diff --git a/src/main/kotlin/snyk/common/CustomEndpoints.kt b/src/main/kotlin/snyk/common/CustomEndpoints.kt index 564d6204a..45b65142a 100644 --- a/src/main/kotlin/snyk/common/CustomEndpoints.kt +++ b/src/main/kotlin/snyk/common/CustomEndpoints.kt @@ -126,13 +126,14 @@ fun URI.isOauth() = isSnykGov() fun URI.isDev() = isSnykDomain() && host.lowercase().startsWith("dev.") -fun URI.isFedramp() = isSnykGov() +fun URI.isAnalyticsPermitted() = host != null && + (host.lowercase() == "app.snyk.io" || host.lowercase() == "app.us.snyk.io" || host.lowercase() == "snyk.io") -fun isFedramp(): Boolean { +fun isAnalyticsPermitted(): Boolean { val settings = pluginSettings() return settings.customEndpointUrl ?.let { URI(it) } - ?.isFedramp() ?: false + ?.isAnalyticsPermitted() ?: true } fun URI.isLocalCodeEngine() = pluginSettings().localCodeEngineEnabled == true diff --git a/src/main/kotlin/snyk/common/EnvironmentHelper.kt b/src/main/kotlin/snyk/common/EnvironmentHelper.kt index ed0509b1d..a2fba3a28 100644 --- a/src/main/kotlin/snyk/common/EnvironmentHelper.kt +++ b/src/main/kotlin/snyk/common/EnvironmentHelper.kt @@ -37,7 +37,7 @@ object EnvironmentHelper { environment["SNYK_API"] = endpoint - if (!pluginSettings().usageAnalyticsEnabled || endpointURI.isFedramp()) { + if (!pluginSettings().usageAnalyticsEnabled || !endpointURI.isAnalyticsPermitted()) { environment["SNYK_CFG_DISABLE_ANALYTICS"] = "1" } diff --git a/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt b/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt index 2109e8a7d..882464e14 100644 --- a/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt +++ b/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt @@ -18,7 +18,7 @@ import io.sentry.protocol.SentryId import io.sentry.protocol.SentryRuntime import io.snyk.plugin.pluginSettings import snyk.PropertyLoader -import snyk.common.isFedramp +import snyk.common.isAnalyticsPermitted import snyk.pluginInfo object SentryErrorReporter { @@ -109,7 +109,7 @@ object SentryErrorReporter { if (ApplicationManager.getApplication().isUnitTestMode) return SentryId.EMPTY_ID val settings = pluginSettings() - return if (settings.crashReportingEnabled && !isFedramp()) { + return if (settings.crashReportingEnabled && isAnalyticsPermitted()) { val sentryId = Sentry.captureException(throwable) LOG.info("Sentry event reported: $sentryId") sentryId diff --git a/src/test/kotlin/snyk/common/CustomEndpointsTest.kt b/src/test/kotlin/snyk/common/CustomEndpointsTest.kt index a54bc783c..06c807d62 100644 --- a/src/test/kotlin/snyk/common/CustomEndpointsTest.kt +++ b/src/test/kotlin/snyk/common/CustomEndpointsTest.kt @@ -174,12 +174,14 @@ class CustomEndpointsTest { @Test fun `api snyk path needs token`() { - var uri = "https://api.snyk.io/v1" - assertTrue(needsSnykToken(uri)) - uri = "https://app.eu.snyk.io/api" - assertTrue(needsSnykToken(uri)) - uri = "https://app.au.snyk.io/api" - assertTrue(needsSnykToken(uri)) + val uris = listOf( + "https://api.snyk.io/v1", + "https://app.eu.snyk.io/api", + "https://app.au.snyk.io/api" + ) + uris.forEach { uri -> + assertTrue(needsSnykToken(uri)) + } } @Test @@ -204,14 +206,29 @@ class CustomEndpointsTest { } @Test - fun `isFedramp true for the right URI`() { - val uri = URI("https://app.fedramp.snykgov.io") - assertTrue(uri.isFedramp()) + fun `isAnalyticsPermitted false for URIs not allowed`() { + val uris = listOf( + "https://app.fedramp.snykgov.io", + "https://app.eu.snyk.io/api", + "https://app.au.snyk.io/api" + ) + uris.forEach { uri -> + assertFalse(URI(uri).isAnalyticsPermitted()) + } } @Test - fun `isFedramp false for the right URI`() { - val uri = URI("https://app.fedddramp.snykagov.io") - assertFalse(uri.isFedramp()) + fun `isAnalyticsPermitted true for the right URIs`() { + val uris = listOf( + "https://snyk.io/api", + "https://app.snyk.io", + "https://app.us.snyk.io", + "https://app.snyk.io/api", + "https://app.snyk.io/v1" + ) + + uris.forEach { uri -> + assertTrue(URI(uri).isAnalyticsPermitted()) + } } } diff --git a/src/test/kotlin/snyk/errorHandler/SentryErrorReporterTest.kt b/src/test/kotlin/snyk/errorHandler/SentryErrorReporterTest.kt index a76bfb292..f7ceb9c8f 100644 --- a/src/test/kotlin/snyk/errorHandler/SentryErrorReporterTest.kt +++ b/src/test/kotlin/snyk/errorHandler/SentryErrorReporterTest.kt @@ -11,11 +11,14 @@ import io.sentry.Sentry import io.sentry.protocol.SentryId import io.snyk.plugin.pluginSettings import io.snyk.plugin.services.SnykApplicationSettingsStateService +import junit.framework.TestCase import org.junit.After import org.junit.Before import org.junit.Test import snyk.PluginInformation +import snyk.common.isAnalyticsPermitted import snyk.pluginInfo +import java.net.URI class SentryErrorReporterTest { @Before @@ -43,15 +46,35 @@ class SentryErrorReporterTest { } @Test - fun `captureException should not send exceptions to Sentry when crashReportingEnabled is true and fedramp`() { + fun `captureException should only report if permitted env`() { val settings = mockPluginInformation() setUnitTesting(false) settings.crashReportingEnabled = true - settings.customEndpointUrl = "https://api.fedramp.snykgov.io" + + val uris = listOf( + "https://app.fedramp.snykgov.io", + "https://app.eu.snyk.io/api", + "https://app.au.snyk.io/api" + ) + + uris.forEach { uri -> + settings.customEndpointUrl = uri + + SentryErrorReporter.captureException(RuntimeException("test")) + + verify(exactly = 0) { Sentry.captureException(any()) } + } + } + @Test + fun `captureException should send exceptions to Sentry when URL is a permitted environment`() { + val settings = mockPluginInformation() + setUnitTesting(false) + settings.crashReportingEnabled = true + settings.customEndpointUrl = "https://app.snyk.io" SentryErrorReporter.captureException(RuntimeException("test")) - verify(exactly = 0) { Sentry.captureException(any()) } + verify(exactly = 1) { Sentry.captureException(any()) } } @Test