diff --git a/CHANGELOG.md b/CHANGELOG.md index 4868026ed..23e0125ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Snyk Security Changelog +## [1.1.59] + +### Fixed +- Change Default API Endpoint to https://api.snyk.io. + ## [1.1.58] ### Fixed diff --git a/Snyk.Common/Service/ApiEndpointResolver.cs b/Snyk.Common/Service/ApiEndpointResolver.cs index 0893ae6f6..e0ab644d9 100644 --- a/Snyk.Common/Service/ApiEndpointResolver.cs +++ b/Snyk.Common/Service/ApiEndpointResolver.cs @@ -1,15 +1,18 @@ -namespace Snyk.Common.Service -{ - using System; - using Snyk.Common.Authentication; - using Snyk.Common.Settings; +using System; +using System.Text.RegularExpressions; +using Snyk.Common.Authentication; +using Snyk.Common.Settings; +namespace Snyk.Common.Service +{ /// /// Helper class for resolve API endpoints. It's one place for all endpoint calculations. /// public class ApiEndpointResolver { private readonly ISnykOptions options; + private const string DefaultApiEndpoint = "https://api.snyk.io"; + public const string DefaultAppEndpoint = "https://app.snyk.io"; /// /// Initializes a new instance of the class. @@ -53,7 +56,7 @@ public string GetSnykApiEndpoint() { var customEndpoint = ResolveCustomEndpoint(this.options.CustomEndpoint); - var sastUrl = string.IsNullOrEmpty(customEndpoint) ? "https://snyk.io/api/" : customEndpoint; + var sastUrl = string.IsNullOrEmpty(customEndpoint) ? DefaultApiEndpoint : customEndpoint; return !sastUrl.EndsWith("/") ? $"{sastUrl}/" : sastUrl; } @@ -63,54 +66,45 @@ public string GetSnykApiEndpoint() /// public string GetSnykCodeApiUrl() { - if (this.IsLocalEngine()) + if (IsLocalEngine()) { - return this.options.SastSettings.LocalCodeEngine.Url + "/"; + return options.SastSettings.LocalCodeEngine.Url + "/"; } var endpoint = ResolveCustomEndpoint(this.options.CustomEndpoint); - var uri = new Uri(endpoint); - - var result = uri.Scheme + "://" + uri.Host.Replace("api.", "deeproxy.").Replace("app.", "deeproxy."); - - if (!result.Contains("deeproxy.")) - { - result = uri.Scheme + "://" + "deeproxy." + uri.Host; - } + var result = GetCustomEndpointUrlFromSnykApi(endpoint, "deeproxy"); + return result + "/"; } - private bool IsSnykCodeAvailable(string endpointUrl) - { - var endpoint = ResolveCustomEndpoint(endpointUrl); - var uri = new Uri(endpoint); - return IsSaaS(uri) || IsSingleTenant(uri); - } - /// /// Resolves the custom endpoint. - /// If the endpointUrl is null or empty, then https://snyk.io/api" will be used. + /// If the endpointUrl is null or empty, then https://api.snyk.io" will be used. /// private string ResolveCustomEndpoint(string endpointUrl) { var resolvedEndpoint = string.IsNullOrEmpty(endpointUrl) - ? "https://snyk.io/api" + ? DefaultApiEndpoint : endpointUrl.RemoveTrailingSlashes().Trim().ReplaceFirst("/v1", string.Empty); return resolvedEndpoint; } - /// - /// Checks if the deployment type is SaaS (production or development). - /// - private bool IsSaaS(Uri uri) => - !uri.Host.StartsWith("app") && uri.Host.EndsWith("snyk.io"); - - /// - /// Checks if the deployment type is Single Tenant. - /// - private bool IsSingleTenant(Uri uri) => - uri.Host.StartsWith("app") && uri.Host.EndsWith("snyk.io"); + public static string GetCustomEndpointUrlFromSnykApi(string apiEndpoint, string subdomain) + { + const string regex = @"^(ap[pi]\.)?"; + if (string.IsNullOrEmpty(subdomain)) + throw new ArgumentException("subdomain must have a value to calculate the result endpoint"); + + if (string.IsNullOrEmpty(apiEndpoint) || !Uri.IsWellFormedUriString(apiEndpoint, UriKind.Absolute)) + return string.Empty; + + var endpointUri = new Uri(apiEndpoint); + + var host = Regex.Replace(endpointUri.Host, regex, $"{subdomain}."); + var uriBuilder = new UriBuilder(endpointUri.Scheme, host); + return uriBuilder.ToString().RemoveTrailingSlashes(); + } private bool IsLocalEngine() => this.options.SastSettings?.LocalCodeEngineEnabled ?? false; } diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs index da65e73e8..6580059b3 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs @@ -1,21 +1,21 @@ -namespace Snyk.VisualStudio.Extension.Shared.Settings +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Authentication; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.VisualStudio.Shell; +using Serilog; +using Snyk.Common; +using Snyk.Common.Authentication; +using Snyk.Common.Service; +using Snyk.Common.Settings; +using Snyk.VisualStudio.Extension.Shared.Service; +using Snyk.VisualStudio.Extension.Shared.UI.Notifications; + +namespace Snyk.VisualStudio.Extension.Shared.Settings { - using System; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Security.Authentication; - using System.Threading.Tasks; - using System.Windows.Forms; - using Microsoft.VisualStudio.Shell; - using Serilog; - using Snyk.Common; - using Snyk.Common.Authentication; - using Snyk.Common.Service; - using Snyk.Common.Settings; - using Snyk.VisualStudio.Extension.Shared.Service; - using Snyk.VisualStudio.Extension.Shared.UI.Notifications; - /// /// Snyk general settings page. /// @@ -23,14 +23,13 @@ [ComVisible(true)] public class SnykGeneralOptionsDialogPage : DialogPage, ISnykOptions { - - public String Application { get; set; } - public String ApplicationVersion { get; set; } - public String IntegrationName { get; } = SnykExtension.IntegrationName; - public String IntegrationVersion { get; } = SnykExtension.Version; - public String IntegrationEnvironment { get; set; } - public String IntegrationEnvironmentVersion { get; set;} - + public string Application { get; set; } + public string ApplicationVersion { get; set; } + public string IntegrationName { get; } = SnykExtension.IntegrationName; + public string IntegrationVersion { get; } = SnykExtension.Version; + public string IntegrationEnvironment { get; set; } + public string IntegrationEnvironmentVersion { get; set;} + private ISnykServiceProvider serviceProvider; private SnykUserStorageSettingsService userStorageSettingsService; @@ -117,9 +116,9 @@ public bool IsFedramp() /// public bool IsAnalyticsPermitted() { - var endpointUri = new Uri(this.GetBaseAppURL()); + var endpointUri = new Uri(this.GetBaseAppUrl()); - string[] permittedHosts = new string[] { "app.snyk.io", "app.us.snyk.io" }; + var permittedHosts = new string[] { "app.snyk.io", "app.us.snyk.io" }; return permittedHosts.Contains(endpointUri.Host.ToLower()); } @@ -153,7 +152,7 @@ public string CustomEndpoint } /// - public string SnykCodeSettingsUrl => $"{this.GetBaseAppURL()}/manage/snyk-code"; + public string SnykCodeSettingsUrl => $"{this.GetBaseAppUrl()}/manage/snyk-code"; public SastSettings SastSettings { @@ -416,23 +415,14 @@ public bool Authenticate() private void FireSettingsChangedEvent() => this.SettingsChanged?.Invoke(this, new SnykSettingsChangedEventArgs()); - private string GetBaseAppURL() + private string GetBaseAppUrl() { - var endpoint = this.customEndpoint.IsNullOrEmpty() ? "https://app.snyk.io" : this.customEndpoint.RemoveTrailingSlashes(); - Uri uri = new Uri(endpoint); + if (string.IsNullOrEmpty(customEndpoint)) + return ApiEndpointResolver.DefaultAppEndpoint; - if (!uri.Host.StartsWith("app") && (uri.Host.EndsWith("snyk.io") || uri.Host.EndsWith("snykgov.io"))) - { - return endpoint.Replace("https://", "https://app.").RemoveFromEnd("/api"); - } - else if (uri.Host.StartsWith("app") && (uri.Host.EndsWith("snyk.io") || uri.Host.EndsWith("snykgov.io"))) - { - return endpoint.RemoveFromEnd("/api"); - } - else - { - return "https://app.snyk.io"; - } + var result = ApiEndpointResolver.GetCustomEndpointUrlFromSnykApi(customEndpoint, "app"); + + return string.IsNullOrEmpty(result) ? ApiEndpointResolver.DefaultAppEndpoint : result; } private void ThrowFileNotFoundException() diff --git a/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs index 437b9d958..d98e73a53 100644 --- a/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs +++ b/Snyk.VisualStudio.Extension.Tests/GeneralOptionsDialogPage.cs @@ -30,12 +30,18 @@ public void ApiEndpointChanged_InvalidatesCliToken() } [Theory] - [InlineData("https://app.snyk.io/api", true)] [InlineData("https://snyk.io/api", true)] + [InlineData("https://app.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)] + + [InlineData("https://api.snyk.io", true)] + [InlineData("https://api.us.snyk.io", true)] + [InlineData("https://api.eu.snyk.io", false)] + [InlineData("https://api.au.snyk.io", false)] + [InlineData("https://api.snykgov.io", false)] public void IsAnalyticsPermitted(string endpoint, bool expected) { var optionsDialogPage = new SnykGeneralOptionsDialogPage(); @@ -53,7 +59,10 @@ public void IsAnalyticsPermitted(string endpoint, bool expected) [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")] + [InlineData("https://api.snyk.io", "https://app.snyk.io/manage/snyk-code")] + [InlineData("https://api.snyk.io/", "https://app.snyk.io/manage/snyk-code")] + [InlineData("https://api.snykgov.io", "https://app.snykgov.io/manage/snyk-code")] + [InlineData("https://api.eu.snyk.io", "https://app.eu.snyk.io/manage/snyk-code")] public void SnykCodeSettingsUrl(string endpoint, string expected) { var optionsDialogPage = new SnykGeneralOptionsDialogPage();