From c6c82fb04801ba0ea22eb7a787bfa41e79dfa04d Mon Sep 17 00:00:00 2001
From: Christof <106981178+chdorner-snyk@users.noreply.github.com>
Date: Wed, 7 Feb 2024 14:40:43 +0100
Subject: [PATCH] feat: only send Amplitude analytics events when connected to
an MT US environment [IDE-24]
* fix: only send analytics when connected to US-based, non-FedRAMP MT environments
* fix: app hostname when custom endpoint is fedramp
This was driving the link in the settings page to enable Snyk Code, if the endpoint
was set to a FedRAMP environment, this still linked to app.snyk.io.
* fix: SnykCodeSettingsUrl edge cases and add tests
* chore: use parametrized tests for `IsAnalyticsPermitted`
---
CHANGELOG.md | 3 ++
Snyk.Common/Settings/ISnykOptions.cs | 2 ++
.../CLI/SnykConsoleRunner.cs | 2 +-
.../Service/SnykService.cs | 2 +-
.../Settings/SnykGeneralOptionsDialogPage.cs | 27 +++++++++++----
.../Toolwindow/SnykToolWindowControl.xaml.cs | 2 +-
.../GeneralOptionsDialogPage.cs | 34 +++++++++++++++++++
.../SnykCliTest.cs | 4 +++
.../SnykConsoleRunnerTest.cs | 28 +++++++--------
9 files changed, 80 insertions(+), 24 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4838c0f36..81fa5fd34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Snyk Changelog
+## [1.1.46]
+- Only send Amplitude analytics events when connected to an MT US environment
+
## [1.1.45]
### Changed
diff --git a/Snyk.Common/Settings/ISnykOptions.cs b/Snyk.Common/Settings/ISnykOptions.cs
index 506052232..ca11e4c5a 100644
--- a/Snyk.Common/Settings/ISnykOptions.cs
+++ b/Snyk.Common/Settings/ISnykOptions.cs
@@ -24,6 +24,8 @@ public interface ISnykOptions
bool IsFedramp();
+ bool IsAnalyticsPermitted();
+
///
/// Gets or sets a value indicating whether CLI custom endpoint parameter.
///
diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs b/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs
index 093684b5e..223ed3e88 100644
--- a/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs
+++ b/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs
@@ -78,7 +78,7 @@ public virtual Process CreateProcess(string fileName, string arguments, StringDi
}
}
- if (!this.options.UsageAnalyticsEnabled || this.options.IsFedramp())
+ if (!this.options.UsageAnalyticsEnabled || !this.options.IsAnalyticsPermitted())
{
processStartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"] = "1";
}
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
index 2b57e103d..a423eee78 100644
--- a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
@@ -319,7 +319,7 @@ private void InitializeAnalyticsService()
string anonymousId = this.Options.AnonymousId;
- var enabled = this.Options.UsageAnalyticsEnabled && !this.Options.IsFedramp();
+ var enabled = this.Options.UsageAnalyticsEnabled && this.Options.IsAnalyticsPermitted();
var endpoint = this.ApiEndpointResolver.UserMeEndpoint;
Logger.Information("analytics enabled = {Enabled}, endpoint = {Endpoint}", enabled, endpoint);
diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs
index c8ebc483f..43d447a7d 100644
--- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs
@@ -2,6 +2,7 @@
{
using System;
using System.IO;
+ using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Threading.Tasks;
@@ -111,6 +112,18 @@ public bool IsFedramp()
return endpointUri.Host.ToLower().EndsWith("snykgov.io");
}
+ ///
+ /// Checks if the current endpoint permits sending external analytics events
+ ///
+ ///
+ public bool IsAnalyticsPermitted()
+ {
+ var endpointUri = new Uri(this.GetBaseAppURL());
+
+ string[] permittedHosts = ["app.snyk.io", "app.us.snyk.io"];
+ return permittedHosts.Contains(endpointUri.Host.ToLower());
+ }
+
///
/// Gets or sets a value indicating whether Custom endpoint.
///
@@ -141,7 +154,7 @@ public string CustomEndpoint
}
///
- public string SnykCodeSettingsUrl => $"{this.GetAppCustomEndpoint()}/manage/snyk-code";
+ public string SnykCodeSettingsUrl => $"{this.GetBaseAppURL()}/manage/snyk-code";
public SastSettings SastSettings
{
@@ -383,22 +396,22 @@ public bool Authenticate()
private void FireSettingsChangedEvent() => this.SettingsChanged?.Invoke(this, new SnykSettingsChangedEventArgs());
- private string GetAppCustomEndpoint()
+ private string GetBaseAppURL()
{
var endpoint = this.customEndpoint.IsNullOrEmpty() ? "https://app.snyk.io" : this.customEndpoint.RemoveTrailingSlashes();
Uri uri = new Uri(endpoint);
- if (!uri.Host.StartsWith("app") && uri.Host.EndsWith("snyk.io"))
+ if (!uri.Host.StartsWith("app") && (uri.Host.EndsWith("snyk.io") || uri.Host.EndsWith("snykgov.io")))
{
- return endpoint.Replace("https://", "https://app.").RemoveFromEnd("api");
+ return endpoint.Replace("https://", "https://app.").RemoveFromEnd("/api");
}
- else if (uri.Host.StartsWith("app") && uri.Host.EndsWith("snyk.io"))
+ else if (uri.Host.StartsWith("app") && (uri.Host.EndsWith("snyk.io") || uri.Host.EndsWith("snykgov.io")))
{
- return endpoint.RemoveFromEnd("api");
+ return endpoint.RemoveFromEnd("/api");
}
else
{
- return "https://app.snyk.io/";
+ return "https://app.snyk.io";
}
}
diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs
index ec432889f..1dae6cca5 100644
--- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs
+++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs
@@ -676,7 +676,7 @@ private void ShowWelcomeOrRunScanScreen()
{
var options = this.serviceProvider.Options;
this.serviceProvider.AnalyticsService.AnalyticsEnabledOption =
- options.UsageAnalyticsEnabled && !options.IsFedramp();
+ options.UsageAnalyticsEnabled && options.IsAnalyticsPermitted();
if (options.ApiToken.IsValid())
{
diff --git a/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs
index 4ec3ff0ad..437b9d958 100644
--- a/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs
+++ b/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs
@@ -1,5 +1,7 @@
namespace Snyk.VisualStudio.Extension.Tests
{
+ using System.Collections.Generic;
+ using System.Net;
using Moq;
using Snyk.VisualStudio.Extension.Shared.CLI;
using Snyk.VisualStudio.Extension.Shared.Service;
@@ -26,5 +28,37 @@ public void ApiEndpointChanged_InvalidatesCliToken()
// Assert
cliMock.Verify(mock => mock.UnsetApiToken());
}
+
+ [Theory]
+ [InlineData("https://app.snyk.io/api", true)]
+ [InlineData("https://snyk.io/api", true)]
+ [InlineData("https://app.us.snyk.io/api", true)]
+ [InlineData("https://app.eu.snyk.io/api", false)]
+ [InlineData("https://app.au.snyk.io/api", false)]
+ [InlineData("https://app.snykgov.io/api", false)]
+ public void IsAnalyticsPermitted(string endpoint, bool expected)
+ {
+ var optionsDialogPage = new SnykGeneralOptionsDialogPage();
+ optionsDialogPage.CustomEndpoint = endpoint;
+ Assert.Equal(expected, optionsDialogPage.IsAnalyticsPermitted());
+ }
+
+ [Theory]
+ [InlineData(null, "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("", "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("https://snyk.io", "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("https://snyk.io/api", "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("https://snyk.io/api/", "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("https://snykgov.io/api", "https://app.snykgov.io/manage/snyk-code")]
+ [InlineData("https://app.snyk.io/api", "https://app.snyk.io/manage/snyk-code")]
+ [InlineData("https://app.eu.snyk.io/api", "https://app.eu.snyk.io/manage/snyk-code")]
+ [InlineData("https://app.snykgov.io/api", "https://app.snykgov.io/manage/snyk-code")]
+ [InlineData("https://example.org", "https://app.snyk.io/manage/snyk-code")]
+ public void SnykCodeSettingsUrl(string endpoint, string expected)
+ {
+ var optionsDialogPage = new SnykGeneralOptionsDialogPage();
+ optionsDialogPage.CustomEndpoint = endpoint;
+ Assert.Equal(expected, optionsDialogPage.SnykCodeSettingsUrl);
+ }
}
}
\ No newline at end of file
diff --git a/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs b/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs
index f5ad819ce..d33dff6b8 100644
--- a/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs
+++ b/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs
@@ -27,6 +27,10 @@ public SnykCliTest()
.Setup(options => options.IsFedramp())
.Returns(false);
+ this.optionsMock
+ .Setup(options => options.IsAnalyticsPermitted())
+ .Returns(true);
+
this.optionsMock
.Setup(options => options.ApiToken)
.Returns(AuthenticationToken.EmptyToken);
diff --git a/Snyk.VisualStudio.Extension.Tests/SnykConsoleRunnerTest.cs b/Snyk.VisualStudio.Extension.Tests/SnykConsoleRunnerTest.cs
index 7f9084bf9..c61ae2998 100644
--- a/Snyk.VisualStudio.Extension.Tests/SnykConsoleRunnerTest.cs
+++ b/Snyk.VisualStudio.Extension.Tests/SnykConsoleRunnerTest.cs
@@ -12,7 +12,7 @@ public class SnykConsoleRunnerTest
{
[Fact]
- public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsFedramp()
+ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsAnalyticsNotPermitted()
{
var optionsMock = new Mock();
optionsMock
@@ -20,8 +20,8 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsFedramp
.Returns(false);
optionsMock
- .Setup(options => options.IsFedramp())
- .Returns(true);
+ .Setup(options => options.IsAnalyticsPermitted())
+ .Returns(false);
var cut = new SnykConsoleRunner(optionsMock.Object);
@@ -31,7 +31,7 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsFedramp
}
[Fact]
- public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsNotFedramp()
+ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsAnalyticsPermitted()
{
var optionsMock = new Mock();
optionsMock
@@ -39,8 +39,8 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsNotFedr
.Returns(false);
optionsMock
- .Setup(options => options.IsFedramp())
- .Returns(false);
+ .Setup(options => options.IsAnalyticsPermitted())
+ .Returns(true);
var cut = new SnykConsoleRunner(optionsMock.Object);
@@ -48,9 +48,9 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_Off_IsNotFedr
Assert.Equal("1", process.StartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"]);
}
-
+
[Fact]
- public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsFedramp()
+ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsAnalyticsPermitted()
{
var optionsMock = new Mock();
optionsMock
@@ -58,18 +58,18 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsFedramp(
.Returns(true);
optionsMock
- .Setup(options => options.IsFedramp())
+ .Setup(options => options.IsAnalyticsPermitted())
.Returns(true);
var cut = new SnykConsoleRunner(optionsMock.Object);
var process = cut.CreateProcess("snyk", "test");
- Assert.Equal("1", process.StartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"]);
+ Assert.Null(process.StartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"]);
}
-
+
[Fact]
- public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsNotFedramp()
+ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsAnalyticsNotPermitted()
{
var optionsMock = new Mock();
optionsMock
@@ -77,14 +77,14 @@ public void SnykConsoleRunnerTest_CreateProcess_Respects_Analytics_On_IsNotFedra
.Returns(true);
optionsMock
- .Setup(options => options.IsFedramp())
+ .Setup(options => options.IsAnalyticsPermitted())
.Returns(false);
var cut = new SnykConsoleRunner(optionsMock.Object);
var process = cut.CreateProcess("snyk", "test");
- Assert.Null(process.StartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"]);
+ Assert.Equal("1", process.StartInfo.EnvironmentVariables["SNYK_CFG_DISABLE_ANALYTICS"]);
}
}
}
\ No newline at end of file