From 9a05faae741fbfab2a0dbcc3df3452dbb031afef Mon Sep 17 00:00:00 2001 From: Asaf Agami Date: Wed, 20 Jul 2022 12:59:00 +0300 Subject: [PATCH] [ROAD-957] Added option for custom CLI paths and auto-update disables (#196) * chore: move from shared project * Removed WebBrowser * fix: using label instead of HTML * fix: remove redundant label * chore: move back to shared project * refactor: removed redundant code * feat: add option to block CLI auto-updates * Revert "chore: move back to shared project" This reverts commit 15983b6104c902a8366e5dae4d8997cee90f1203. * Added custom CLI path to view * Added custom cli path to options * Cli custom path with textbox * fix divide by null * change textbox to open-file dialog * chore: move back to shared project * fix: use custom cli instead of default one in SnykCli.cs * chore: cleanup * refactor: Added constructor for ChecksumVerificationException * Remove redundant code * fix: isCliDownloading not being set correctly * fix: added finally block to guarantee isCliDownloading = false * refactor: remove OnUiLoaded from SnykTasksService * Remove redundant code * refactor: make panels readonly * fix: Set API token in options on SetupApiToken * fix: specify Task in using directives and fix typo * refactor: Made DisposeCancellationTokenSource static * feat: Added properties to ChecksumVerificationException * feat: Logging the hashes when ChecksumVerificationException is raised * refactor: Removed RetryDownloadAsync * refactor: removed callbacks and fixed progressbar staying visible during CLI download * refactor: Authenticate throws FileNotFoundException * refactor: renamed progressBar * feat: show dog logo on message panel * feat: show cli not found message * refactor: use JTF instead of Task.Run * feat: CLI custom path textbox enabled * refactor: remove redundant tag * refactor: add DownloadFailed event * feat: implemented different flow for CLI download fails * feat: removed Snyk logo from messagePanel * feat: improved message to the user * fix: fixed the case where download fails but CLI already exists * fix: fire settings changed event after changing Custom CLI path * feat: allow CLI custom executable selection only from the browse/clear buttons in settings * fix: broken tests * refactor: removed redundant code * fix: Snyk.Common keeps getting recompiled * fix: settings.json file gets deleted every time we build * refactor: removed DoesCliExist(string) * fix: cli auto-update false but cli is missing case * fix: same thing but for real this time * docs: changelog.md * fix: settings.json deletion on pre-2022 extension * chore: move settings.json instead of copy * fix: token disposing nullifies the reference * docs: changelog.md * Revert "fix: token disposing nullifies the reference" This reverts commit a43ba8668ffb657e2bbc35341350f458794e25c3. * chore: removed unused directives * feat: renamed labels in settings * feat: changed settings UI * chore: csproj reformatted by VS * refactor: renamed CliAutoUpdate to BinariesAutoUpdate * docs: added docs to SnykCliDownloader.GetCliFilePath --- CHANGELOG.md | 14 +- Snyk.Common/Snyk.Common.csproj | 4 +- .../Snyk.VisualStudio.Extension.2022.csproj | 10 + .../Download/ChecksumVerificationException.cs | 13 +- .../CLI/Download/SnykCliDownloader.cs | 62 ++-- .../CLI/ICli.cs | 7 + .../CLI/SnykCli.cs | 41 ++- .../CLI/SnykConsoleRunner.cs | 1 - .../Commands/AbstractTaskCommand.cs | 1 - .../Commands/SnykCleanPanelCommand.cs | 1 - .../Microsoft/HtmlParser/HtmlCssParser.cs | 1 - .../Service/ISnykProgressWorker.cs | 2 +- .../Service/SentryService.cs | 1 - .../Service/SnykService.cs | 2 +- .../Service/SnykTasksService.cs | 152 +++++---- .../Settings/ISnykOptions.cs | 15 +- .../Settings/SnykGeneralOptionsDialogPage.cs | 35 +- ...SnykGeneralSettingsUserControl.Designer.cs | 309 ++++++++++++------ .../SnykGeneralSettingsUserControl.cs | 124 ++++--- .../SnykGeneralSettingsUserControl.resx | 30 +- .../Settings/SnykSettings.cs | 27 +- .../Settings/SnykSolutionOptionsDialogPage.cs | 1 - .../SnykSolutionOptionsUserControl.cs | 1 - .../SnykUserStorageSettingsService.cs | 22 ++ ...yk.VisualStudio.Extension.Shared.projitems | 66 ++-- .../SnykVSPackage.cs | 2 - .../Theme/SnykVsThemeService.cs | 1 - .../UI/Notifications/NotificationService.cs | 1 - .../UI/Notifications/VsStatusBar.cs | 9 +- .../VsStatusBarNotificationService.cs | 5 + .../UI/Toolwindow/MessagePanel.xaml | 11 +- .../UI/Toolwindow/MessagePanel.xaml.cs | 71 ++-- .../Toolwindow/SnykToolWindowControl.xaml.cs | 31 +- .../SnykCliTest.cs | 43 +-- .../Snyk.VisualStudio.Extension.csproj | 23 ++ 35 files changed, 702 insertions(+), 437 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4002ffe1..9eb2a3c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,24 @@ # Snyk Changelog +## [1.1.25] + +### Added +- Option to disable CLI auto-update. +- Option to select CLI custom path. +- Improved UI/UX when the CLI is missing. + +### Fixed +- Several issues with auto-updating the CLI executable. + ## [1.1.24] ### Fixed - - Extension errors on VS2017. +- Extension fails to load on VS2017. ## [1.1.23] ### Added - - Organization description information in settings. +- Organization description information in settings. ### Fixed - Changing custom endpoint settings leads to authentication errors. diff --git a/Snyk.Common/Snyk.Common.csproj b/Snyk.Common/Snyk.Common.csproj index b574fa400..a88f86497 100644 --- a/Snyk.Common/Snyk.Common.csproj +++ b/Snyk.Common/Snyk.Common.csproj @@ -70,12 +70,12 @@ - Always + PreserveNewest - Always + PreserveNewest diff --git a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj index 4725f8f83..2d9fd61e2 100644 --- a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj +++ b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj @@ -27,6 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp + $(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile true @@ -129,4 +130,13 @@ --> + + + + + + + + + \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/Download/ChecksumVerificationException.cs b/Snyk.VisualStudio.Extension.Shared/CLI/Download/ChecksumVerificationException.cs index 156f6f6cb..244b0e97c 100644 --- a/Snyk.VisualStudio.Extension.Shared/CLI/Download/ChecksumVerificationException.cs +++ b/Snyk.VisualStudio.Extension.Shared/CLI/Download/ChecksumVerificationException.cs @@ -7,13 +7,14 @@ /// public class ChecksumVerificationException : Exception { - /// - /// Initializes a new instance of the class. - /// - /// Exception message. - public ChecksumVerificationException(string message) - : base(message) + public string ExpectedHash { get; } + public string ActualHash { get; } + + public ChecksumVerificationException(string expectedHash, string actualHash) + : base($"Expected {expectedHash}, but downloaded file has {actualHash}") { + this.ExpectedHash = expectedHash; + this.ActualHash = actualHash; } } } diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/Download/SnykCliDownloader.cs b/Snyk.VisualStudio.Extension.Shared/CLI/Download/SnykCliDownloader.cs index 5169712f8..52c8ba5b8 100644 --- a/Snyk.VisualStudio.Extension.Shared/CLI/Download/SnykCliDownloader.cs +++ b/Snyk.VisualStudio.Extension.Shared/CLI/Download/SnykCliDownloader.cs @@ -23,36 +23,35 @@ public class SnykCliDownloader private static readonly ILogger Logger = LogManager.ForContext(); - private string currentCliVersion; + private readonly string currentCliVersion; private string expectedSha; - /// - /// Initializes a new instance of the class. - /// - /// ActivityLogger parameter. - public SnykCliDownloader() - { - } - /// /// Initializes a new instance of the class. /// /// Initial CLI version parameter. - /// ActivityLogger parameter. - public SnykCliDownloader(string currentCliVersion) - : this() => this.currentCliVersion = currentCliVersion; + public SnykCliDownloader(string currentCliVersion) => this.currentCliVersion = currentCliVersion; /// /// Callback on download finished event. /// public delegate void CliDownloadFinishedCallback(); + /// + /// Gets the valid CLI path. When a custom CLI path is specified, it returns the custom path. + /// When the Custom CLI path is null or empty, it returns the default CLI path. + /// + /// The custom CLI path from the settings. + /// If is null or empty, the default path would be returned. + private static string GetCliFilePath(string customCliPath) => string.IsNullOrEmpty(customCliPath) + ? SnykCli.GetSnykCliDefaultPath() + : customCliPath; + /// /// Request last cli information. /// /// Latest CLI relaese information. - /// public LatestReleaseInfo GetLatestReleaseInfo() { Logger.Information("Enter GetLatestReleaseInfo method"); @@ -76,7 +75,6 @@ public LatestReleaseInfo GetLatestReleaseInfo() /// Request last cli sha. /// /// CLI sha string. - /// public string GetLatestCliSha() { Logger.Information("Enter GetLatestCliSha method"); @@ -158,7 +156,7 @@ public bool IsCliDownloadNeeded(DateTime lastCheckDate, string cliFileDestinatio /// Last check date. /// True if new version CLI exists public bool IsCliUpdateExists(DateTime lastCheckDate) => this.IsFourDaysPassedAfterLastCheck(lastCheckDate) - && this.IsNewVersionAvailable(this.currentCliVersion, this.GetLatestReleaseInfo().Name); + && this.IsNewVersionAvailable(this.currentCliVersion, this.GetLatestReleaseInfo().Version); /// /// Check is there a new version on the server and if there is, download it. @@ -174,14 +172,16 @@ public async Task AutoUpdateCliAsync( string filePath = null, List downloadFinishedCallbacks = null) { - string fileDestinationPath = this.GetCliFilePath(filePath); + string fileDestinationPath = GetCliFilePath(filePath); + + var isCliDownloadNeeded = this.IsCliDownloadNeeded(lastCheckDate, fileDestinationPath); - if (this.IsCliDownloadNeeded(lastCheckDate, fileDestinationPath)) + if (isCliDownloadNeeded) { await this.DownloadAsync( - fileDestinationPath: fileDestinationPath, - progressWorker: progressWorker, - downloadFinishedCallbacks: downloadFinishedCallbacks); + progressWorker, + fileDestinationPath, + downloadFinishedCallbacks); } else { @@ -203,7 +203,7 @@ public async Task DownloadAsync( { Logger.Information("Enter Download method"); - string cliFileDestinationPath = this.GetCliFilePath(fileDestinationPath); + string cliFileDestinationPath = GetCliFilePath(fileDestinationPath); Logger.Information("CLI File Destination Path: {Path}", cliFileDestinationPath); @@ -215,8 +215,6 @@ public async Task DownloadAsync( LatestReleaseInfo latestReleaseInfo = this.GetLatestReleaseInfo(); - string cliDownloadUrl = string.Format(LatestReleaseDownloadUrl, latestReleaseInfo.Version, SnykCli.CliFileName); - Logger.Information("Latest relase information: version {Version} and url {Url}", latestReleaseInfo.Version, latestReleaseInfo.Url); progressWorker.CancelIfCancellationRequested(); @@ -239,14 +237,14 @@ public void VerifyCliFile(string cliPath) { if (!this.IsCliFileExists(cliPath)) { - throw new ChecksumVerificationException("Cli file not exists, can't verify checksum"); + throw new FileNotFoundException($"Cli file not found in {cliPath}"); } string currentSha = Sha256.Checksum(cliPath); if (this.expectedSha.ToLower() != currentSha.ToLower()) { - throw new ChecksumVerificationException($"Expected {this.expectedSha}, but downloaded file has {currentSha}"); + throw new ChecksumVerificationException(this.expectedSha, currentSha); } } @@ -265,7 +263,7 @@ private void PrepareSnykCliDirectory() } } - private async Task DownloadAsync( + public async Task DownloadAsync( ISnykProgressWorker progressWorker, string cliFileDestinationPath, string cliDownloadUrl, @@ -293,6 +291,8 @@ private async Task DownloadFileAsync( string cliFileDestinationPath, List downloadFinishedCallbacks = null) { + const int bufferSize = 8192; + using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(5); @@ -303,13 +303,13 @@ private async Task DownloadFileAsync( string tempCliFile = Path.GetTempFileName(); - using (var fileStream = new FileStream(tempCliFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, 8192, true)) + using (var fileStream = new FileStream(tempCliFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, bufferSize, true)) { using (Stream contentStream = await response.Content.ReadAsStreamAsync()) { - var totalBytes = response.Content.Headers.ContentLength; + var totalBytes = response.Content.Headers.ContentLength ?? long.MaxValue; // Avoid dividing by null when calculating progress var totalRead = 0L; - var buffer = new byte[8192]; + var buffer = new byte[bufferSize]; var isMoreToRead = true; var lastProgressPercentage = 0; @@ -394,12 +394,10 @@ private int CliVersionAsInt(string cliVersion) { return int.Parse(cliVersion.Replace(".", string.Empty)); } - catch (FormatException e) + catch (FormatException) { return -1; } } - - private string GetCliFilePath(string filePath) => string.IsNullOrEmpty(filePath) ? SnykCli.GetSnykCliPath() : filePath; } } diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/ICli.cs b/Snyk.VisualStudio.Extension.Shared/CLI/ICli.cs index e8891cceb..a6e7015e2 100644 --- a/Snyk.VisualStudio.Extension.Shared/CLI/ICli.cs +++ b/Snyk.VisualStudio.Extension.Shared/CLI/ICli.cs @@ -29,5 +29,12 @@ public interface ICli /// Unsets the API token stored in the config file in ~/.config/configstore/snyk.json /// void UnsetApiToken(); + + /// + /// Checks if the CLI executable exists. + /// Checks the custom path specified in the settings, or the default path if the custom path is not specified. + /// + /// true if CLI executable is found, false otherwise. + bool IsCliFileFound(); } } diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/SnykCli.cs b/Snyk.VisualStudio.Extension.Shared/CLI/SnykCli.cs index 0c80cda14..585dc4ef1 100644 --- a/Snyk.VisualStudio.Extension.Shared/CLI/SnykCli.cs +++ b/Snyk.VisualStudio.Extension.Shared/CLI/SnykCli.cs @@ -28,7 +28,11 @@ public class SnykCli : ICli /// /// Initializes a new instance of the class. /// - public SnykCli() => this.ConsoleRunner = new SnykConsoleRunner(); + public SnykCli(ISnykOptions options) + { + this.ConsoleRunner = new SnykConsoleRunner(); + this.options = options; + } /// /// Gets or sets a value indicating whether (settings). @@ -39,7 +43,6 @@ public ISnykOptions Options { return this.options; } - set { this.options = value; @@ -53,13 +56,10 @@ public ISnykOptions Options /// Get Snyk CLI file path. /// /// CLI path string. - public static string GetSnykCliPath() => Path.Combine(SnykDirectory.GetSnykAppDataDirectoryPath(), CliFileName); - - /// - /// Check is CLI file exists in $UserDirectory\.AppData\Snyk. - /// - /// True if CLI file exists. - public static bool IsCliExists() => File.Exists(GetSnykCliPath()); + public static string GetSnykCliDefaultPath() + { + return Path.Combine(SnykDirectory.GetSnykAppDataDirectoryPath(), CliFileName); + } /// /// Safely get Snyk API token from settings. @@ -84,7 +84,15 @@ public string GetApiToken() } /// - public void UnsetApiToken() => this.ConsoleRunner.Run(GetSnykCliPath(), "config unset api"); + public void UnsetApiToken() => this.ConsoleRunner.Run(this.GetCliPath(), "config unset api"); + + /// + public bool IsCliFileFound() + { + var customPath = this.Options.CliCustomPath; + var path = string.IsNullOrEmpty(customPath) ? GetSnykCliDefaultPath() : customPath; + return File.Exists(path); + } /// /// Try get Snyk API token from snyk cli config or throw . @@ -92,7 +100,7 @@ public string GetApiToken() /// API token string. public string GetApiTokenOrThrowException() { - string apiToken = this.ConsoleRunner.Run(GetSnykCliPath(), "config get api"); + string apiToken = this.ConsoleRunner.Run(this.GetCliPath(), "config get api"); if (!Guid.IsValid(apiToken)) { @@ -120,7 +128,7 @@ public string Authenticate() environmentVariables.Add(ApiEnvironmentVariableName, this.Options.CustomEndpoint); } - return this.ConsoleRunner.Run(GetSnykCliPath(), string.Join(" ", args), environmentVariables); + return this.ConsoleRunner.Run(this.GetCliPath(), string.Join(" ", args), environmentVariables); } /// @@ -128,7 +136,7 @@ public async Task ScanAsync(string basePath) { Logger.Information("Path to scan {BasePath}", basePath); - var cliPath = GetSnykCliPath(); + var cliPath = this.GetCliPath(); Logger.Information("CLI path is {CliPath}", cliPath); @@ -145,6 +153,13 @@ public async Task ScanAsync(string basePath) return ConvertRawCliStringToCliResult(consoleResult); } + private string GetCliPath() + { + var snykCliCustomPath = this.options?.CliCustomPath; + var cliPath = string.IsNullOrEmpty(snykCliCustomPath) ? GetSnykCliDefaultPath() : snykCliCustomPath; + return cliPath; + } + public StringDictionary BuildScanEnvironmentVariables() { var environmentVariables = new StringDictionary(); diff --git a/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs b/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs index 8536d4a36..3d52111e6 100644 --- a/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs +++ b/Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs @@ -2,7 +2,6 @@ { using System; using System.Collections; - using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Text; diff --git a/Snyk.VisualStudio.Extension.Shared/Commands/AbstractTaskCommand.cs b/Snyk.VisualStudio.Extension.Shared/Commands/AbstractTaskCommand.cs index 1ae333606..13ba98583 100644 --- a/Snyk.VisualStudio.Extension.Shared/Commands/AbstractTaskCommand.cs +++ b/Snyk.VisualStudio.Extension.Shared/Commands/AbstractTaskCommand.cs @@ -3,7 +3,6 @@ using System; using Microsoft.VisualStudio.Shell; using Snyk.VisualStudio.Extension.Shared.CLI; - using Snyk.VisualStudio.Extension.Shared.Service; /// /// Common class for and task commands. diff --git a/Snyk.VisualStudio.Extension.Shared/Commands/SnykCleanPanelCommand.cs b/Snyk.VisualStudio.Extension.Shared/Commands/SnykCleanPanelCommand.cs index 2e217a852..e0957de06 100644 --- a/Snyk.VisualStudio.Extension.Shared/Commands/SnykCleanPanelCommand.cs +++ b/Snyk.VisualStudio.Extension.Shared/Commands/SnykCleanPanelCommand.cs @@ -3,7 +3,6 @@ using System; using System.ComponentModel.Design; using Microsoft.VisualStudio.Shell; - using Snyk.VisualStudio.Extension.Shared.UI.Notifications; using Task = System.Threading.Tasks.Task; /// diff --git a/Snyk.VisualStudio.Extension.Shared/Microsoft/HtmlParser/HtmlCssParser.cs b/Snyk.VisualStudio.Extension.Shared/Microsoft/HtmlParser/HtmlCssParser.cs index e5cfb04d0..a6a156a58 100644 --- a/Snyk.VisualStudio.Extension.Shared/Microsoft/HtmlParser/HtmlCssParser.cs +++ b/Snyk.VisualStudio.Extension.Shared/Microsoft/HtmlParser/HtmlCssParser.cs @@ -13,7 +13,6 @@ namespace Microsoft.HtmlParser using System.Collections; using System.Collections.Generic; using System.Diagnostics; - using System.Text; using System.Xml; using Microsoft.HtmlConverter; diff --git a/Snyk.VisualStudio.Extension.Shared/Service/ISnykProgressWorker.cs b/Snyk.VisualStudio.Extension.Shared/Service/ISnykProgressWorker.cs index 8490d89df..ac8b5f620 100644 --- a/Snyk.VisualStudio.Extension.Shared/Service/ISnykProgressWorker.cs +++ b/Snyk.VisualStudio.Extension.Shared/Service/ISnykProgressWorker.cs @@ -32,7 +32,7 @@ public interface ISnykProgressWorker void CancelIfCancellationRequested(); /// - /// Notify donwload cancelled. + /// Notify download cancelled. /// /// Cancelled message. void DownloadCancelled(string message); diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SentryService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SentryService.cs index 64dab560d..0f712daad 100644 --- a/Snyk.VisualStudio.Extension.Shared/Service/SentryService.cs +++ b/Snyk.VisualStudio.Extension.Shared/Service/SentryService.cs @@ -2,7 +2,6 @@ { using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; - using Microsoft.VisualStudio.Threading; using Sentry; using Snyk.Common; using Task = System.Threading.Tasks.Task; diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs index 16409fb9b..8ac6cf53f 100644 --- a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs +++ b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs @@ -258,7 +258,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken) /// Create new instance of SnykCli class with Options and Logger parameters. /// /// New SnykCli instance. - public ICli NewCli() => new SnykCli { Options = this.Options, }; + public ICli NewCli() => new SnykCli(this.Options); /// /// Check is Options.ApiToken initialized. But if it's empty it will call CLI.GetApiToken() method. diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs index bbf95d59e..17a705c08 100644 --- a/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs +++ b/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using System.Windows; using Microsoft.VisualStudio.Shell; using Serilog; using Snyk.Analytics; @@ -112,10 +111,15 @@ private SnykTasksService() public event EventHandler DownloadUpdate; /// - /// Download cancelled event handler. + /// Download cancelled event handler. Raised when the user cancels the download intentionally. /// public event EventHandler DownloadCancelled; + /// + /// Download failed event handler. Raised when the download fails due to an error. + /// + public event EventHandler DownloadFailed; + /// /// Task finished event. /// @@ -182,13 +186,6 @@ public void CancelTasks() } } - /// - /// Handle UI loaded event. Check CLI download on this event. - /// - /// Source object. - /// Event arguments. - public void OnUiLoaded(object sender, RoutedEventArgs eventArgs) => this.Download(); - /// /// Start scan in background task. /// @@ -224,7 +221,7 @@ public async Task ScanAsync() } /// - /// Start download task in background thread. + /// Start a CLI download task in background thread. /// /// callback object. public void Download(CliDownloadFinishedCallback downloadFinishedCallback = null) @@ -249,8 +246,7 @@ public void Download(CliDownloadFinishedCallback downloadFinishedCallback = null }; Logger.Information("Start run task"); - - _ = Task.Run( + ThreadHelper.JoinableTaskFactory.RunAsync( async () => { try @@ -259,28 +255,37 @@ public void Download(CliDownloadFinishedCallback downloadFinishedCallback = null } catch (ChecksumVerificationException e) { - Logger.Error(e, "Cli download failed. Checksum don't match. Try to download again..."); + Logger.Error( + e, + "Cli download failed due to failed checksum. {Expected} (expected) != {Actual}", + e.ExpectedHash, + e.ActualHash); - await this.RetryDownloadAsync(downloadFinishedCallback, progressWorker); + this.OnDownloadFailed(e); + } + catch (OperationCanceledException e) + { + Logger.Information("CLI Download cancelled"); + this.OnDownloadCancelled(e.Message); } catch (Exception e) { Logger.Error(e, "Error on cli download"); - this.OnDownloadCancelled(e.Message); + this.OnDownloadFailed(e); } finally { + this.isCliDownloading = false; + if (progressWorker.IsWorkFinished) { - this.isCliDownloading = false; - - this.DisposeCancellationTokenSource(this.downloadCliTokenSource); + DisposeCancellationTokenSource(this.downloadCliTokenSource); this.FireTaskFinished(); } } - }, progressWorker.TokenSource.Token); + }).FireAndForget(); } catch (Exception ex) { @@ -346,12 +351,30 @@ protected internal void OnUpdateDownloadStarted() /// Cancel message. protected internal void OnDownloadCancelled(string message) => this.DownloadCancelled?.Invoke(this, new SnykCliDownloadEventArgs(message)); + /// + /// Fire download cancelled event. + /// + /// The exception that caused the download to fail. + protected internal void OnDownloadFailed(Exception exception) => this.DownloadFailed?.Invoke(this, exception); + /// /// Fire download update (on download progress update) event. /// /// Donwload progress form 0..100$. protected internal void OnDownloadUpdate(int progress) => this.DownloadUpdate?.Invoke(this, new SnykCliDownloadEventArgs(progress)); + private static void DisposeCancellationTokenSource(CancellationTokenSource tokenSource) + { + try + { + tokenSource?.Dispose(); + } + catch (Exception e) + { + Logger.Error(e, "Error when trying to dispose cancellation token source"); + } + } + private async Task ScanOssAsync(FeaturesSettings featuresSettings) { try @@ -439,7 +462,7 @@ private async Task RunOssScanAsync(FeaturesSettings featuresSettings) } finally { - this.DisposeCancellationTokenSource(this.ossScanTokenSource); + DisposeCancellationTokenSource(this.ossScanTokenSource); this.isOssScanning = false; @@ -521,7 +544,7 @@ private async Task RunSnykCodeScanAsync(CancellationToken cancellationToken) } finally { - this.DisposeCancellationTokenSource(this.snykCodeScanTokenSource); + DisposeCancellationTokenSource(this.snykCodeScanTokenSource); this.isSnykCodeScanning = false; @@ -660,78 +683,51 @@ private void CancelTask(CancellationTokenSource tokenSource) } } - private void DisposeCancellationTokenSource(CancellationTokenSource tokenSource) - { - try - { - tokenSource?.Dispose(); - } - catch (Exception e) - { - Logger.Error(e, "Try to dispose token source."); - } - finally - { - tokenSource = null; - } - } - private async Task DownloadAsync(CliDownloadFinishedCallback downloadFinishedCallback, ISnykProgressWorker progressWorker) { - this.isCliDownloading = true; - - var userStorageService = this.serviceProvider.UserStorageSettingsService; - - string currentCliVersion = userStorageService.GetCurrentCliVersion(); - - DateTime lastCliReleaseDate = userStorageService.GetCliReleaseLastCheckDate(); - - var cliDownloader = new SnykCliDownloader(currentCliVersion); - - List downloadFinishedCallbacks = new List(); - - if (downloadFinishedCallback != null) + var userSettingsStorageService = this.serviceProvider.UserStorageSettingsService; + if (!userSettingsStorageService.BinariesAutoUpdate) { - downloadFinishedCallbacks.Add(downloadFinishedCallback); + Logger.Information("CLI auto-update is disabled, CLI download is skipped."); + this.DownloadCancelled?.Invoke(this, new SnykCliDownloadEventArgs()); + return; } - downloadFinishedCallbacks.Add(new CliDownloadFinishedCallback(() => + this.isCliDownloading = true; + try { - userStorageService.SaveCurrentCliVersion(cliDownloader.GetLatestReleaseInfo().Name); - userStorageService.SaveCliReleaseLastCheckDate(DateTime.UtcNow); + string currentCliVersion = userSettingsStorageService.GetCurrentCliVersion(); - this.isCliDownloading = false; + DateTime lastCliReleaseDate = userSettingsStorageService.GetCliReleaseLastCheckDate(); - this.DisposeCancellationTokenSource(this.downloadCliTokenSource); - })); + var cliDownloader = new SnykCliDownloader(currentCliVersion); - await cliDownloader.AutoUpdateCliAsync( - progressWorker, - lastCliReleaseDate, - downloadFinishedCallbacks: downloadFinishedCallbacks); - } + List downloadFinishedCallbacks = new List(); - private async Task RetryDownloadAsync(CliDownloadFinishedCallback downloadFinishedCallback, SnykProgressWorker progressWorker) - { - try - { - await this.DownloadAsync(downloadFinishedCallback, progressWorker); - } - catch (Exception e) - { - Logger.Error(e, "Cli retry download failed"); + if (downloadFinishedCallback != null) + { + downloadFinishedCallbacks.Add(downloadFinishedCallback); + } - this.OnDownloadCancelled($"The download of the Snyk CLI was not successful. The integrity check failed ({e.Message})"); + downloadFinishedCallbacks.Add(() => + { + userSettingsStorageService.SaveCurrentCliVersion(cliDownloader.GetLatestReleaseInfo().Name); + userSettingsStorageService.SaveCliReleaseLastCheckDate(DateTime.UtcNow); + DisposeCancellationTokenSource(this.downloadCliTokenSource); + }); + + var downloadPath = this.serviceProvider.Options.CliCustomPath; + await cliDownloader.AutoUpdateCliAsync( + progressWorker, + lastCliReleaseDate, + downloadPath, + downloadFinishedCallbacks: downloadFinishedCallbacks); } finally { - if (progressWorker.IsWorkFinished) - { - this.isCliDownloading = false; - - this.DisposeCancellationTokenSource(this.downloadCliTokenSource); - } + this.isCliDownloading = false; } } + } } \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/ISnykOptions.cs b/Snyk.VisualStudio.Extension.Shared/Settings/ISnykOptions.cs index b373178c2..e8e231f81 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/ISnykOptions.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/ISnykOptions.cs @@ -63,6 +63,16 @@ public interface ISnykOptions /// bool UsageAnalyticsEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the CLI should be automatically updated. + /// + bool BinariesAutoUpdate { get; set; } + + /// + /// Gets or sets the value of the CLI custom path. If empty, the default path from AppData would be used. + /// + string CliCustomPath { get; set; } + /// /// Gets a value indicating whether additional options. /// Get this data using . @@ -80,9 +90,8 @@ public interface ISnykOptions /// /// Call CLI auth for user authentication at Snyk and get user api token. /// - /// Callback for success authentication case. - /// Callback for error on authentication case. - void Authenticate(Action successCallbackAction, Action errorCallbackAction); + /// Returns true if authenticated successfully, false otherwise. + bool Authenticate(); /// /// Force Visual Studio to load Settings from storage. diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs index ffacb4c19..4bbe476ee 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralOptionsDialogPage.cs @@ -162,6 +162,32 @@ public bool UsageAnalyticsEnabled set => this.userStorageSettingsService?.SaveUsageAnalyticsEnabled(value); } + /// + public bool BinariesAutoUpdate + { + get => this.userStorageSettingsService.BinariesAutoUpdate; + set + { + if (this.userStorageSettingsService != null) + { + this.userStorageSettingsService.BinariesAutoUpdate = value; + } + } + } + + public string CliCustomPath + { + get => this.userStorageSettingsService.CliCustomPath; + set + { + if (this.userStorageSettingsService != null) + { + this.userStorageSettingsService.CliCustomPath = value; + this.FireSettingsChangedEvent(); + } + } + } + /// public string AnonymousId { @@ -217,13 +243,8 @@ public void Initialize(ISnykServiceProvider provider) this.userStorageSettingsService = this.serviceProvider.UserStorageSettingsService; } - /// - /// Authenticate CLI. - /// - /// Success callback. - /// Error callback. - public void Authenticate(Action successCallbackAction, Action errorCallbackAction) - => this.GeneralSettingsUserControl.Authenticate(successCallbackAction, errorCallbackAction); + /// + public bool Authenticate() => this.GeneralSettingsUserControl.Authenticate(); private void FireSettingsChangedEvent() => this.SettingsChanged?.Invoke(this, new SnykSettingsChangedEventArgs()); diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.Designer.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.Designer.cs index a254fde30..4832ab04d 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.Designer.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.Designer.cs @@ -49,6 +49,15 @@ private void InitializeComponent() this.codeSecurityEnabledCheckBox = new System.Windows.Forms.CheckBox(); this.codeQualityEnabledCheckBox = new System.Windows.Forms.CheckBox(); this.generalSettingsGroupBox = new System.Windows.Forms.GroupBox(); + this.OrganizationInfoLink = new System.Windows.Forms.LinkLabel(); + this.OrgDescriptionText = new System.Windows.Forms.Label(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.resetCliPathToDefaultButton = new System.Windows.Forms.Button(); + this.CliPathBrowseButton = new System.Windows.Forms.Button(); + this.CliPathTextBox = new System.Windows.Forms.TextBox(); + this.CliPathLabel = new System.Windows.Forms.Label(); + this.ManageBinariesAutomaticallyCheckbox = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); this.productSelectionGroupBox = new System.Windows.Forms.GroupBox(); this.snykCodeQualityInfoLabel = new System.Windows.Forms.Label(); this.snykCodeSecurityInfoLabel = new System.Windows.Forms.Label(); @@ -60,19 +69,21 @@ private void InitializeComponent() this.ossInfoToolTip = new System.Windows.Forms.ToolTip(this.components); this.snykCodeSecurityInfoToolTip = new System.Windows.Forms.ToolTip(this.components); this.snykCodeQualityInfoToolTip = new System.Windows.Forms.ToolTip(this.components); - this.orgInfoWebBrowser = new System.Windows.Forms.WebBrowser(); + this.customCliPathFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.ExecutablesGroupBox = new System.Windows.Forms.GroupBox(); ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit(); this.generalSettingsGroupBox.SuspendLayout(); this.productSelectionGroupBox.SuspendLayout(); this.userExperienceGroupBox.SuspendLayout(); + this.ExecutablesGroupBox.SuspendLayout(); this.SuspendLayout(); // // customEndpointTextBox // - this.customEndpointTextBox.Location = new System.Drawing.Point(200, 165); - this.customEndpointTextBox.Margin = new System.Windows.Forms.Padding(4); + this.customEndpointTextBox.Location = new System.Drawing.Point(100, 86); + this.customEndpointTextBox.Margin = new System.Windows.Forms.Padding(2); this.customEndpointTextBox.Name = "customEndpointTextBox"; - this.customEndpointTextBox.Size = new System.Drawing.Size(836, 31); + this.customEndpointTextBox.Size = new System.Drawing.Size(420, 20); this.customEndpointTextBox.TabIndex = 0; this.customEndpointTextBox.LostFocus += new System.EventHandler(this.CustomEndpointTextBox_LostFocus); this.customEndpointTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.CustomEndpointTextBox_Validating); @@ -80,49 +91,48 @@ private void InitializeComponent() // customEndpointLabel // this.customEndpointLabel.AutoSize = true; - this.customEndpointLabel.Location = new System.Drawing.Point(8, 171); - this.customEndpointLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.customEndpointLabel.Location = new System.Drawing.Point(4, 89); + this.customEndpointLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.customEndpointLabel.Name = "customEndpointLabel"; - this.customEndpointLabel.Size = new System.Drawing.Size(180, 25); + this.customEndpointLabel.Size = new System.Drawing.Size(89, 13); this.customEndpointLabel.TabIndex = 1; this.customEndpointLabel.Text = "Custom endpoint:"; // // organizationLabel // this.organizationLabel.AutoSize = true; - this.organizationLabel.Location = new System.Drawing.Point(8, 260); - this.organizationLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.organizationLabel.Location = new System.Drawing.Point(4, 135); + this.organizationLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.organizationLabel.Name = "organizationLabel"; - this.organizationLabel.Size = new System.Drawing.Size(140, 25); + this.organizationLabel.Size = new System.Drawing.Size(69, 13); this.organizationLabel.TabIndex = 2; this.organizationLabel.Text = "Organization:"; // // organizationTextBox // - this.organizationTextBox.Location = new System.Drawing.Point(200, 258); - this.organizationTextBox.Margin = new System.Windows.Forms.Padding(4); + this.organizationTextBox.Location = new System.Drawing.Point(100, 134); + this.organizationTextBox.Margin = new System.Windows.Forms.Padding(2); this.organizationTextBox.Name = "organizationTextBox"; - this.organizationTextBox.Size = new System.Drawing.Size(836, 32); + this.organizationTextBox.Size = new System.Drawing.Size(420, 20); this.organizationTextBox.TabIndex = 3; this.organizationTextBox.TextChanged += new System.EventHandler(this.OrganizationTextBox_TextChanged); // // tokenLabel // this.tokenLabel.AutoSize = true; - this.tokenLabel.Location = new System.Drawing.Point(8, 110); - this.tokenLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.tokenLabel.Location = new System.Drawing.Point(4, 57); + this.tokenLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.tokenLabel.Name = "tokenLabel"; - this.tokenLabel.Size = new System.Drawing.Size(78, 25); + this.tokenLabel.Size = new System.Drawing.Size(41, 13); this.tokenLabel.TabIndex = 4; this.tokenLabel.Text = "Token:"; // // tokenTextBox // - this.tokenTextBox.Location = new System.Drawing.Point(200, 104); - this.tokenTextBox.Margin = new System.Windows.Forms.Padding(6); + this.tokenTextBox.Location = new System.Drawing.Point(100, 54); this.tokenTextBox.Name = "tokenTextBox"; this.tokenTextBox.PasswordChar = '*'; - this.tokenTextBox.Size = new System.Drawing.Size(836, 31); + this.tokenTextBox.Size = new System.Drawing.Size(420, 20); this.tokenTextBox.TabIndex = 5; this.tokenTextBox.TextChanged += new System.EventHandler(this.TokenTextBox_TextChanged); this.tokenTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.TokenTextBox_Validating); @@ -130,10 +140,10 @@ private void InitializeComponent() // ignoreUnknownCACheckBox // this.ignoreUnknownCACheckBox.AutoSize = true; - this.ignoreUnknownCACheckBox.Location = new System.Drawing.Point(200, 206); - this.ignoreUnknownCACheckBox.Margin = new System.Windows.Forms.Padding(4); + this.ignoreUnknownCACheckBox.Location = new System.Drawing.Point(100, 107); + this.ignoreUnknownCACheckBox.Margin = new System.Windows.Forms.Padding(2); this.ignoreUnknownCACheckBox.Name = "ignoreUnknownCACheckBox"; - this.ignoreUnknownCACheckBox.Size = new System.Drawing.Size(231, 29); + this.ignoreUnknownCACheckBox.Size = new System.Drawing.Size(120, 17); this.ignoreUnknownCACheckBox.TabIndex = 6; this.ignoreUnknownCACheckBox.Text = "Ignore unknown CA"; this.ignoreUnknownCACheckBox.UseVisualStyleBackColor = true; @@ -141,10 +151,9 @@ private void InitializeComponent() // // authenticateButton // - this.authenticateButton.Location = new System.Drawing.Point(200, 38); - this.authenticateButton.Margin = new System.Windows.Forms.Padding(6); + this.authenticateButton.Location = new System.Drawing.Point(100, 20); this.authenticateButton.Name = "authenticateButton"; - this.authenticateButton.Size = new System.Drawing.Size(386, 38); + this.authenticateButton.Size = new System.Drawing.Size(193, 20); this.authenticateButton.TabIndex = 7; this.authenticateButton.Text = "Connect Visual Studio to Snyk.io"; this.authenticateButton.UseVisualStyleBackColor = true; @@ -152,11 +161,11 @@ private void InitializeComponent() // // authProgressBar // - this.authProgressBar.Location = new System.Drawing.Point(200, 146); - this.authProgressBar.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6); + this.authProgressBar.Location = new System.Drawing.Point(100, 76); + this.authProgressBar.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.authProgressBar.MarqueeAnimationSpeed = 10; this.authProgressBar.Name = "authProgressBar"; - this.authProgressBar.Size = new System.Drawing.Size(840, 10); + this.authProgressBar.Size = new System.Drawing.Size(420, 5); this.authProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; this.authProgressBar.TabIndex = 8; this.authProgressBar.Visible = false; @@ -170,10 +179,10 @@ private void InitializeComponent() this.usageAnalyticsCheckBox.AutoSize = true; this.usageAnalyticsCheckBox.Checked = true; this.usageAnalyticsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.usageAnalyticsCheckBox.Location = new System.Drawing.Point(24, 58); - this.usageAnalyticsCheckBox.Margin = new System.Windows.Forms.Padding(4); + this.usageAnalyticsCheckBox.Location = new System.Drawing.Point(12, 30); + this.usageAnalyticsCheckBox.Margin = new System.Windows.Forms.Padding(2); this.usageAnalyticsCheckBox.Name = "usageAnalyticsCheckBox"; - this.usageAnalyticsCheckBox.Size = new System.Drawing.Size(250, 29); + this.usageAnalyticsCheckBox.Size = new System.Drawing.Size(127, 17); this.usageAnalyticsCheckBox.TabIndex = 9; this.usageAnalyticsCheckBox.Text = "Send usage analytics"; this.usageAnalyticsCheckBox.UseVisualStyleBackColor = true; @@ -184,10 +193,10 @@ private void InitializeComponent() this.ossEnabledCheckBox.AutoSize = true; this.ossEnabledCheckBox.Checked = true; this.ossEnabledCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.ossEnabledCheckBox.Location = new System.Drawing.Point(24, 58); - this.ossEnabledCheckBox.Margin = new System.Windows.Forms.Padding(4); + this.ossEnabledCheckBox.Location = new System.Drawing.Point(12, 30); + this.ossEnabledCheckBox.Margin = new System.Windows.Forms.Padding(2); this.ossEnabledCheckBox.Name = "ossEnabledCheckBox"; - this.ossEnabledCheckBox.Size = new System.Drawing.Size(362, 29); + this.ossEnabledCheckBox.Size = new System.Drawing.Size(182, 17); this.ossEnabledCheckBox.TabIndex = 11; this.ossEnabledCheckBox.Text = "Snyk Open Source vulnerabilities"; this.ossEnabledCheckBox.UseVisualStyleBackColor = true; @@ -198,10 +207,10 @@ private void InitializeComponent() this.codeSecurityEnabledCheckBox.AutoSize = true; this.codeSecurityEnabledCheckBox.Checked = true; this.codeSecurityEnabledCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.codeSecurityEnabledCheckBox.Location = new System.Drawing.Point(24, 104); - this.codeSecurityEnabledCheckBox.Margin = new System.Windows.Forms.Padding(4); + this.codeSecurityEnabledCheckBox.Location = new System.Drawing.Point(12, 54); + this.codeSecurityEnabledCheckBox.Margin = new System.Windows.Forms.Padding(2); this.codeSecurityEnabledCheckBox.Name = "codeSecurityEnabledCheckBox"; - this.codeSecurityEnabledCheckBox.Size = new System.Drawing.Size(371, 29); + this.codeSecurityEnabledCheckBox.Size = new System.Drawing.Size(185, 17); this.codeSecurityEnabledCheckBox.TabIndex = 12; this.codeSecurityEnabledCheckBox.Text = "Snyk Code Security vulnerabilities"; this.codeSecurityEnabledCheckBox.UseVisualStyleBackColor = true; @@ -212,10 +221,10 @@ private void InitializeComponent() this.codeQualityEnabledCheckBox.AutoSize = true; this.codeQualityEnabledCheckBox.Checked = true; this.codeQualityEnabledCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.codeQualityEnabledCheckBox.Location = new System.Drawing.Point(484, 104); - this.codeQualityEnabledCheckBox.Margin = new System.Windows.Forms.Padding(4); + this.codeQualityEnabledCheckBox.Location = new System.Drawing.Point(242, 54); + this.codeQualityEnabledCheckBox.Margin = new System.Windows.Forms.Padding(2); this.codeQualityEnabledCheckBox.Name = "codeQualityEnabledCheckBox"; - this.codeQualityEnabledCheckBox.Size = new System.Drawing.Size(290, 29); + this.codeQualityEnabledCheckBox.Size = new System.Drawing.Size(145, 17); this.codeQualityEnabledCheckBox.TabIndex = 13; this.codeQualityEnabledCheckBox.Text = "Snyk Code Quality issues"; this.codeQualityEnabledCheckBox.UseVisualStyleBackColor = true; @@ -223,7 +232,8 @@ private void InitializeComponent() // // generalSettingsGroupBox // - this.generalSettingsGroupBox.Controls.Add(this.orgInfoWebBrowser); + this.generalSettingsGroupBox.Controls.Add(this.OrganizationInfoLink); + this.generalSettingsGroupBox.Controls.Add(this.OrgDescriptionText); this.generalSettingsGroupBox.Controls.Add(this.tokenLabel); this.generalSettingsGroupBox.Controls.Add(this.tokenTextBox); this.generalSettingsGroupBox.Controls.Add(this.authProgressBar); @@ -233,15 +243,108 @@ private void InitializeComponent() this.generalSettingsGroupBox.Controls.Add(this.organizationLabel); this.generalSettingsGroupBox.Controls.Add(this.ignoreUnknownCACheckBox); this.generalSettingsGroupBox.Controls.Add(this.organizationTextBox); - this.generalSettingsGroupBox.Location = new System.Drawing.Point(20, 20); - this.generalSettingsGroupBox.Margin = new System.Windows.Forms.Padding(16, 15, 16, 15); + this.generalSettingsGroupBox.Location = new System.Drawing.Point(10, 10); + this.generalSettingsGroupBox.Margin = new System.Windows.Forms.Padding(8); this.generalSettingsGroupBox.Name = "generalSettingsGroupBox"; - this.generalSettingsGroupBox.Padding = new System.Windows.Forms.Padding(4); - this.generalSettingsGroupBox.Size = new System.Drawing.Size(1550, 440); + this.generalSettingsGroupBox.Padding = new System.Windows.Forms.Padding(2); + this.generalSettingsGroupBox.Size = new System.Drawing.Size(560, 233); this.generalSettingsGroupBox.TabIndex = 17; this.generalSettingsGroupBox.TabStop = false; this.generalSettingsGroupBox.Text = "General Settings"; // + // OrganizationInfoLink + // + this.OrganizationInfoLink.AutoSize = true; + this.OrganizationInfoLink.Location = new System.Drawing.Point(108, 205); + this.OrganizationInfoLink.Name = "OrganizationInfoLink"; + this.OrganizationInfoLink.Size = new System.Drawing.Size(150, 13); + this.OrganizationInfoLink.TabIndex = 11; + this.OrganizationInfoLink.TabStop = true; + this.OrganizationInfoLink.Text = "Learn more about organization"; + this.OrganizationInfoLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OrganizationInfoLink_LinkClicked); + // + // OrgDescriptionText + // + this.OrgDescriptionText.AutoSize = true; + this.OrgDescriptionText.Location = new System.Drawing.Point(108, 156); + this.OrgDescriptionText.Name = "OrgDescriptionText"; + this.OrgDescriptionText.Size = new System.Drawing.Size(376, 39); + this.OrgDescriptionText.TabIndex = 10; + this.OrgDescriptionText.Text = "Specify an organization slug name to run tests for that organization.\r\nIt must ma" + + "tch the URL slug as displayed in the URL of your org in the Snyk UI:\r\nhttps://ap" + + "p.snyk.io/org/[OrgSlugName]"; + // + // richTextBox1 + // + this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.richTextBox1.Location = new System.Drawing.Point(7, 106); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.ReadOnly = true; + this.richTextBox1.Size = new System.Drawing.Size(513, 32); + this.richTextBox1.TabIndex = 18; + this.richTextBox1.Text = "Snyk will download, install and update the dependencies for you. If this option i" + + "s disabled, make sure valid paths to the dependencies are provided."; + // + // resetCliPathToDefaultButton + // + this.resetCliPathToDefaultButton.Location = new System.Drawing.Point(179, 26); + this.resetCliPathToDefaultButton.Name = "resetCliPathToDefaultButton"; + this.resetCliPathToDefaultButton.Size = new System.Drawing.Size(97, 23); + this.resetCliPathToDefaultButton.TabIndex = 17; + this.resetCliPathToDefaultButton.Text = "Reset to default"; + this.resetCliPathToDefaultButton.UseVisualStyleBackColor = true; + this.resetCliPathToDefaultButton.Click += new System.EventHandler(this.ClearCliCustomPathButton_Click); + // + // CliPathBrowseButton + // + this.CliPathBrowseButton.Location = new System.Drawing.Point(98, 26); + this.CliPathBrowseButton.Name = "CliPathBrowseButton"; + this.CliPathBrowseButton.Size = new System.Drawing.Size(75, 23); + this.CliPathBrowseButton.TabIndex = 16; + this.CliPathBrowseButton.Text = "Browse"; + this.CliPathBrowseButton.UseVisualStyleBackColor = true; + this.CliPathBrowseButton.Click += new System.EventHandler(this.CliPathBrowseButton_Click); + // + // CliPathTextBox + // + this.CliPathTextBox.Location = new System.Drawing.Point(100, 51); + this.CliPathTextBox.Margin = new System.Windows.Forms.Padding(2); + this.CliPathTextBox.Name = "CliPathTextBox"; + this.CliPathTextBox.ReadOnly = true; + this.CliPathTextBox.Size = new System.Drawing.Size(420, 20); + this.CliPathTextBox.TabIndex = 15; + // + // CliPathLabel + // + this.CliPathLabel.AutoSize = true; + this.CliPathLabel.Location = new System.Drawing.Point(4, 31); + this.CliPathLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.CliPathLabel.Name = "CliPathLabel"; + this.CliPathLabel.Size = new System.Drawing.Size(78, 13); + this.CliPathLabel.TabIndex = 14; + this.CliPathLabel.Text = "Snyk CLI Path:"; + // + // ManageBinariesAutomaticallyCheckbox + // + this.ManageBinariesAutomaticallyCheckbox.AutoSize = true; + this.ManageBinariesAutomaticallyCheckbox.Location = new System.Drawing.Point(12, 87); + this.ManageBinariesAutomaticallyCheckbox.Margin = new System.Windows.Forms.Padding(2); + this.ManageBinariesAutomaticallyCheckbox.Name = "ManageBinariesAutomaticallyCheckbox"; + this.ManageBinariesAutomaticallyCheckbox.Size = new System.Drawing.Size(15, 14); + this.ManageBinariesAutomaticallyCheckbox.TabIndex = 13; + this.ManageBinariesAutomaticallyCheckbox.UseVisualStyleBackColor = true; + this.ManageBinariesAutomaticallyCheckbox.CheckedChanged += new System.EventHandler(this.ManageBinariesAutomaticallyCheckbox_CheckedChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(31, 87); + this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(253, 13); + this.label1.TabIndex = 12; + this.label1.Text = "Update and install Snyk dependencies automatically"; + // // productSelectionGroupBox // this.productSelectionGroupBox.Controls.Add(this.snykCodeQualityInfoLabel); @@ -253,11 +356,11 @@ private void InitializeComponent() this.productSelectionGroupBox.Controls.Add(this.codeQualityEnabledCheckBox); this.productSelectionGroupBox.Controls.Add(this.ossEnabledCheckBox); this.productSelectionGroupBox.Controls.Add(this.codeSecurityEnabledCheckBox); - this.productSelectionGroupBox.Location = new System.Drawing.Point(20, 475); - this.productSelectionGroupBox.Margin = new System.Windows.Forms.Padding(4); + this.productSelectionGroupBox.Location = new System.Drawing.Point(10, 409); + this.productSelectionGroupBox.Margin = new System.Windows.Forms.Padding(2); this.productSelectionGroupBox.Name = "productSelectionGroupBox"; - this.productSelectionGroupBox.Padding = new System.Windows.Forms.Padding(16, 15, 16, 15); - this.productSelectionGroupBox.Size = new System.Drawing.Size(1550, 250); + this.productSelectionGroupBox.Padding = new System.Windows.Forms.Padding(8); + this.productSelectionGroupBox.Size = new System.Drawing.Size(560, 130); this.productSelectionGroupBox.TabIndex = 18; this.productSelectionGroupBox.TabStop = false; this.productSelectionGroupBox.Text = "Product Selection"; @@ -267,10 +370,10 @@ private void InitializeComponent() this.snykCodeQualityInfoLabel.BackColor = System.Drawing.Color.Transparent; this.snykCodeQualityInfoLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); this.snykCodeQualityInfoLabel.Image = ((System.Drawing.Image)(resources.GetObject("snykCodeQualityInfoLabel.Image"))); - this.snykCodeQualityInfoLabel.Location = new System.Drawing.Point(768, 96); - this.snykCodeQualityInfoLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.snykCodeQualityInfoLabel.Location = new System.Drawing.Point(384, 50); + this.snykCodeQualityInfoLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.snykCodeQualityInfoLabel.Name = "snykCodeQualityInfoLabel"; - this.snykCodeQualityInfoLabel.Size = new System.Drawing.Size(40, 38); + this.snykCodeQualityInfoLabel.Size = new System.Drawing.Size(20, 20); this.snykCodeQualityInfoLabel.TabIndex = 20; this.snykCodeQualityInfoLabel.Text = " "; this.snykCodeQualityInfoToolTip.SetToolTip(this.snykCodeQualityInfoLabel, "Find and fix code quality issues in your application code in real time"); @@ -280,25 +383,25 @@ private void InitializeComponent() this.snykCodeSecurityInfoLabel.BackColor = System.Drawing.Color.Transparent; this.snykCodeSecurityInfoLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); this.snykCodeSecurityInfoLabel.Image = ((System.Drawing.Image)(resources.GetObject("snykCodeSecurityInfoLabel.Image"))); - this.snykCodeSecurityInfoLabel.Location = new System.Drawing.Point(390, 96); + this.snykCodeSecurityInfoLabel.Location = new System.Drawing.Point(195, 50); this.snykCodeSecurityInfoLabel.Margin = new System.Windows.Forms.Padding(0); this.snykCodeSecurityInfoLabel.Name = "snykCodeSecurityInfoLabel"; - this.snykCodeSecurityInfoLabel.Size = new System.Drawing.Size(40, 38); + this.snykCodeSecurityInfoLabel.Size = new System.Drawing.Size(20, 20); this.snykCodeSecurityInfoLabel.TabIndex = 20; this.snykCodeSecurityInfoLabel.Text = " "; - this.snykCodeSecurityInfoToolTip.SetToolTip(this.snykCodeSecurityInfoLabel, "Find and fix vulnerabilities in your application code in real time"); + this.snykCodeSecurityInfoToolTip.SetToolTip(this.snykCodeSecurityInfoLabel, "Find and fix vulnerabilities in your application code in real time"); // // ossInfoLabel // this.ossInfoLabel.BackColor = System.Drawing.Color.Transparent; this.ossInfoLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); this.ossInfoLabel.Image = ((System.Drawing.Image)(resources.GetObject("ossInfoLabel.Image"))); - this.ossInfoLabel.Location = new System.Drawing.Point(380, 50); + this.ossInfoLabel.Location = new System.Drawing.Point(190, 26); this.ossInfoLabel.Margin = new System.Windows.Forms.Padding(0); - this.ossInfoLabel.MaximumSize = new System.Drawing.Size(32, 31); - this.ossInfoLabel.MinimumSize = new System.Drawing.Size(32, 31); + this.ossInfoLabel.MaximumSize = new System.Drawing.Size(16, 16); + this.ossInfoLabel.MinimumSize = new System.Drawing.Size(16, 16); this.ossInfoLabel.Name = "ossInfoLabel"; - this.ossInfoLabel.Size = new System.Drawing.Size(32, 31); + this.ossInfoLabel.Size = new System.Drawing.Size(16, 16); this.ossInfoLabel.TabIndex = 20; this.ossInfoLabel.Text = " "; this.ossInfoToolTip.SetToolTip(this.ossInfoLabel, "Find and automatically fix open source vulnerabilities"); @@ -306,10 +409,10 @@ private void InitializeComponent() // checkAgainLinkLabel // this.checkAgainLinkLabel.AutoSize = true; - this.checkAgainLinkLabel.Location = new System.Drawing.Point(316, 188); - this.checkAgainLinkLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.checkAgainLinkLabel.Location = new System.Drawing.Point(158, 98); + this.checkAgainLinkLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.checkAgainLinkLabel.Name = "checkAgainLinkLabel"; - this.checkAgainLinkLabel.Size = new System.Drawing.Size(132, 25); + this.checkAgainLinkLabel.Size = new System.Drawing.Size(67, 13); this.checkAgainLinkLabel.TabIndex = 16; this.checkAgainLinkLabel.TabStop = true; this.checkAgainLinkLabel.Text = "Check again"; @@ -318,10 +421,10 @@ private void InitializeComponent() // snykCodeSettingsLinkLabel // this.snykCodeSettingsLinkLabel.AutoSize = true; - this.snykCodeSettingsLinkLabel.Location = new System.Drawing.Point(18, 188); - this.snykCodeSettingsLinkLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.snykCodeSettingsLinkLabel.Location = new System.Drawing.Point(9, 98); + this.snykCodeSettingsLinkLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.snykCodeSettingsLinkLabel.Name = "snykCodeSettingsLinkLabel"; - this.snykCodeSettingsLinkLabel.Size = new System.Drawing.Size(291, 25); + this.snykCodeSettingsLinkLabel.Size = new System.Drawing.Size(145, 13); this.snykCodeSettingsLinkLabel.TabIndex = 15; this.snykCodeSettingsLinkLabel.TabStop = true; this.snykCodeSettingsLinkLabel.Text = "Snyk > Settings > Snyk Code"; @@ -330,21 +433,21 @@ private void InitializeComponent() // snykCodeDisabledInfoLabel // this.snykCodeDisabledInfoLabel.AutoSize = true; - this.snykCodeDisabledInfoLabel.Location = new System.Drawing.Point(18, 154); - this.snykCodeDisabledInfoLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.snykCodeDisabledInfoLabel.Location = new System.Drawing.Point(9, 80); + this.snykCodeDisabledInfoLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.snykCodeDisabledInfoLabel.Name = "snykCodeDisabledInfoLabel"; - this.snykCodeDisabledInfoLabel.Size = new System.Drawing.Size(578, 25); + this.snykCodeDisabledInfoLabel.Size = new System.Drawing.Size(282, 13); this.snykCodeDisabledInfoLabel.TabIndex = 14; this.snykCodeDisabledInfoLabel.Text = "Snyk Code is disabled by your organisation\'s configuration:"; // // userExperienceGroupBox // this.userExperienceGroupBox.Controls.Add(this.usageAnalyticsCheckBox); - this.userExperienceGroupBox.Location = new System.Drawing.Point(20, 740); - this.userExperienceGroupBox.Margin = new System.Windows.Forms.Padding(4); + this.userExperienceGroupBox.Location = new System.Drawing.Point(10, 547); + this.userExperienceGroupBox.Margin = new System.Windows.Forms.Padding(2); this.userExperienceGroupBox.Name = "userExperienceGroupBox"; - this.userExperienceGroupBox.Padding = new System.Windows.Forms.Padding(16, 15, 16, 15); - this.userExperienceGroupBox.Size = new System.Drawing.Size(1550, 115); + this.userExperienceGroupBox.Padding = new System.Windows.Forms.Padding(8); + this.userExperienceGroupBox.Size = new System.Drawing.Size(560, 60); this.userExperienceGroupBox.TabIndex = 19; this.userExperienceGroupBox.TabStop = false; this.userExperienceGroupBox.Text = "User experience"; @@ -364,36 +467,40 @@ private void InitializeComponent() this.snykCodeQualityInfoToolTip.IsBalloon = true; this.snykCodeQualityInfoToolTip.ShowAlways = true; // - // webBrowser1 - // - this.orgInfoWebBrowser.Location = new System.Drawing.Point(200, 280); - this.orgInfoWebBrowser.MinimumSize = new System.Drawing.Size(20, 20); - this.orgInfoWebBrowser.Name = "orgInfoWebBrowser"; - this.orgInfoWebBrowser.Size = new System.Drawing.Size(836, 130); - this.orgInfoWebBrowser.TabIndex = 9; - this.orgInfoWebBrowser.ScrollBarsEnabled = false; - this.orgInfoWebBrowser.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right); - this.orgInfoWebBrowser.DocumentText = @"

Specify an organization slug name to run tests for that organization. - It must match the URL slug as displayed in the URL of your org in the Snyk UI:
https://app.snyk.io/org/[orgslugname]

- Learn more about organization

"; - this.orgInfoWebBrowser.Navigating += new WebBrowserNavigatingEventHandler((sender, eventArgs) => - { - eventArgs.Cancel = true; - - Process.Start(eventArgs.Url.ToString()); - }); + // customCliPathFileDialog + // + this.customCliPathFileDialog.Filter = "Snyk CLI|snyk-win.exe"; + this.customCliPathFileDialog.SupportMultiDottedExtensions = true; + // + // ExecutablesGroupBox + // + this.ExecutablesGroupBox.Controls.Add(this.richTextBox1); + this.ExecutablesGroupBox.Controls.Add(this.CliPathLabel); + this.ExecutablesGroupBox.Controls.Add(this.resetCliPathToDefaultButton); + this.ExecutablesGroupBox.Controls.Add(this.label1); + this.ExecutablesGroupBox.Controls.Add(this.CliPathBrowseButton); + this.ExecutablesGroupBox.Controls.Add(this.ManageBinariesAutomaticallyCheckbox); + this.ExecutablesGroupBox.Controls.Add(this.CliPathTextBox); + this.ExecutablesGroupBox.Location = new System.Drawing.Point(10, 254); + this.ExecutablesGroupBox.Name = "ExecutablesGroupBox"; + this.ExecutablesGroupBox.Size = new System.Drawing.Size(560, 152); + this.ExecutablesGroupBox.TabIndex = 19; + this.ExecutablesGroupBox.TabStop = false; + this.ExecutablesGroupBox.Text = "Executables Settings"; // // SnykGeneralSettingsUserControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScroll = true; + this.Controls.Add(this.ExecutablesGroupBox); this.Controls.Add(this.userExperienceGroupBox); this.Controls.Add(this.productSelectionGroupBox); this.Controls.Add(this.generalSettingsGroupBox); - this.Margin = new System.Windows.Forms.Padding(4); - this.MinimumSize = new System.Drawing.Size(1590, 1442); + this.Margin = new System.Windows.Forms.Padding(2); + this.MinimumSize = new System.Drawing.Size(795, 750); this.Name = "SnykGeneralSettingsUserControl"; - this.Size = new System.Drawing.Size(1590, 1442); + this.Size = new System.Drawing.Size(795, 750); ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit(); this.generalSettingsGroupBox.ResumeLayout(false); this.generalSettingsGroupBox.PerformLayout(); @@ -401,6 +508,8 @@ private void InitializeComponent() this.productSelectionGroupBox.PerformLayout(); this.userExperienceGroupBox.ResumeLayout(false); this.userExperienceGroupBox.PerformLayout(); + this.ExecutablesGroupBox.ResumeLayout(false); + this.ExecutablesGroupBox.PerformLayout(); this.ResumeLayout(false); } @@ -433,6 +542,16 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip snykCodeSecurityInfoToolTip; private System.Windows.Forms.Label snykCodeQualityInfoLabel; private System.Windows.Forms.ToolTip snykCodeQualityInfoToolTip; - private System.Windows.Forms.WebBrowser orgInfoWebBrowser; + private LinkLabel OrganizationInfoLink; + private Label OrgDescriptionText; + private Label label1; + private CheckBox ManageBinariesAutomaticallyCheckbox; + private Label CliPathLabel; + private TextBox CliPathTextBox; + private Button CliPathBrowseButton; + private OpenFileDialog customCliPathFileDialog; + private Button resetCliPathToDefaultButton; + private RichTextBox richTextBox1; + private GroupBox ExecutablesGroupBox; } } diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.cs index 971c0c4a2..dae1ecdcf 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.cs @@ -1,7 +1,9 @@ namespace Snyk.VisualStudio.Extension.Shared.Settings { using System; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.VisualStudio.Shell; @@ -11,7 +13,6 @@ using Snyk.VisualStudio.Extension.Shared.CLI; using Snyk.VisualStudio.Extension.Shared.Service; using Snyk.VisualStudio.Extension.Shared.UI.Notifications; - using static Snyk.VisualStudio.Extension.Shared.CLI.Download.SnykCliDownloader; using Task = System.Threading.Tasks.Task; /// @@ -46,6 +47,8 @@ public SnykGeneralSettingsUserControl(ISnykApiService apiService) this.apiService = apiService; } + private ISnykServiceProvider ServiceProvider => this.OptionsDialogPage.ServiceProvider; + /// /// Initialize elements and actions. /// @@ -56,7 +59,7 @@ public void Initialize() this.InitializeApiToken(); this.UpdateViewFromOptionsDialog(); this.OptionsDialogPage.SettingsChanged += this.OptionsDialogPageOnSettingsChanged; - this.Load += new EventHandler(this.SnykGeneralSettingsUserControl_Load); + this.Load += this.SnykGeneralSettingsUserControl_Load; logger.Information("Leave Initialize method"); } @@ -68,6 +71,13 @@ private void UpdateViewFromOptionsDialog() this.ignoreUnknownCACheckBox.Checked = this.OptionsDialogPage.IgnoreUnknownCA; this.usageAnalyticsCheckBox.Checked = this.OptionsDialogPage.UsageAnalyticsEnabled; this.ossEnabledCheckBox.Checked = this.OptionsDialogPage.OssEnabled; + this.ManageBinariesAutomaticallyCheckbox.Checked = this.OptionsDialogPage.BinariesAutoUpdate; + + var cliPath = string.IsNullOrEmpty(this.OptionsDialogPage.CliCustomPath) + ? SnykCli.GetSnykCliDefaultPath() + : this.OptionsDialogPage.CliCustomPath; + + this.CliPathTextBox.Text = cliPath; } private void OptionsDialogPageOnSettingsChanged(object sender, SnykSettingsChangedEventArgs e) @@ -79,31 +89,23 @@ private void OptionsDialogPageOnSettingsChanged(object sender, SnykSettingsChang /// /// Authenticate user via cli auth. /// - /// Callback for success authentication. - /// Callback for fail authentication. - public void Authenticate(Action successCallbackAction, Action errorCallbackAction) + /// Thrown when the CLI could not be found. + /// Returns true if authenticated successfully, false otherwise. + public bool Authenticate() { logger.Information("Enter Authenticate method"); - _ = Task.Run(() => - { - var serviceProvider = this.OptionsDialogPage.ServiceProvider; + var cli = this.ServiceProvider.NewCli(); - if (SnykCli.IsCliExists()) - { - logger.Information("CLI exists. Calling SetupApiToken method"); + if (!cli.IsCliFileFound()) + { + logger.Information("CLI not exists. Download CLI before get Api token"); + throw new FileNotFoundException("CLI was not found"); + } - this.SetupApiToken(successCallbackAction, errorCallbackAction); - } - else - { - logger.Information("CLI not exists. Download CLI before get Api token"); + logger.Information("CLI exists. Calling SetupApiToken method"); - serviceProvider.TasksService.Download(new CliDownloadFinishedCallback(this.OnCliDownloadFinishedCallback)); - } - }); - - logger.Information("Leave Authenticate method"); + return this.SetupApiToken(); } private async Task OnAuthenticationSuccessfulAsync(string apiToken) @@ -144,7 +146,7 @@ private async Task OnAuthenticationSuccessfulAsync(string apiToken) })); } - await this.OptionsDialogPage.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); + await this.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); } private void InitializeApiToken() @@ -197,14 +199,14 @@ private async Task OnAuthenticationFailAsync(string errorMessage) Path = string.Empty, }; - this.OptionsDialogPage.ServiceProvider.TasksService.FireOssError(cliError); + this.ServiceProvider.TasksService.FireOssError(cliError); - this.OptionsDialogPage.ServiceProvider.ToolWindow.Show(); + this.ServiceProvider.ToolWindow.Show(); - await this.OptionsDialogPage.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); + await this.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); } - private SnykCli NewCli() => new SnykCli { Options = this.OptionsDialogPage }; + private SnykCli NewCli() => new SnykCli(this.OptionsDialogPage); private void AuthenticateButton_Click(object sender, EventArgs eventArgs) => ThreadHelper.JoinableTaskFactory .RunAsync(this.AuthenticateButtonClickAsync); @@ -220,9 +222,10 @@ private async Task AuthenticateButtonClickAsync() logger.Information("Start run task"); await TaskScheduler.Default; - var serviceProvider = this.OptionsDialogPage.ServiceProvider; + var serviceProvider = this.ServiceProvider; - if (SnykCli.IsCliExists()) + var cli = this.ServiceProvider.NewCli(); + if (cli.IsCliFileFound()) { logger.Information("CLI exists. Calling SetupApiToken method"); @@ -298,37 +301,29 @@ private async Task SetupApiTokenAsync() } } - private void SetupApiToken(Action successCallback, Action errorCallback) + private bool SetupApiToken() { logger.Information("Enter SetupApiToken method"); - - string apiToken; - try { logger.Information("Try get Api token"); - - apiToken = this.NewCli().GetApiToken(); + var apiToken = this.NewCli().GetApiToken(); if (string.IsNullOrEmpty(apiToken)) { logger.Information("Api toke is null or empty. Try to authenticate via snyk auth"); - string authResultMessage = this.NewCli().Authenticate(); if (authResultMessage.Contains("Your account has been authenticated. Snyk is now ready to be used.")) { logger.Information("Snyk auth executed successfully. Try to get Api token"); - apiToken = this.NewCli().GetApiToken(); } else { logger.Information("Snyk auth executed with error: {AuthResultMessage}", authResultMessage); - - errorCallback(authResultMessage); - - return; + NotificationService.Instance.ShowErrorInfoBar(authResultMessage); + return false; } } @@ -336,20 +331,18 @@ private void SetupApiToken(Action successCallback, Action errorC if (!Common.Guid.IsValid(apiToken)) { - errorCallback($"Invalid GUID: {apiToken}"); - - return; + NotificationService.Instance.ShowErrorInfoBar($"Invalid API Token: {apiToken}"); + return false; } - successCallback(apiToken); + this.OptionsDialogPage.ApiToken = apiToken; + return true; - logger.Information("Leave SetupApiToken method"); } catch (Exception e) { logger.Error(e, "Setup api token in general settings"); - - errorCallback(e.Message); + return false; } } @@ -382,7 +375,7 @@ private void IgnoreUnknownCACheckBox_CheckedChanged(object sender, EventArgs e) private void TokenTextBox_Validating(object sender, System.ComponentModel.CancelEventArgs cancelEventArgs) => ThreadHelper.JoinableTaskFactory.RunAsync(async () => { - await this.OptionsDialogPage.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); + await this.ServiceProvider.ToolWindow.UpdateScreenStateAsync(); if (string.IsNullOrEmpty(this.tokenTextBox.Text)) { @@ -553,7 +546,7 @@ private void UsageAnalyticsCheckBox_CheckedChanged(object sender, EventArgs e) = { this.OptionsDialogPage.UsageAnalyticsEnabled = this.usageAnalyticsCheckBox.Checked; - var serviceProvider = this.OptionsDialogPage.ServiceProvider; + var serviceProvider = this.ServiceProvider; serviceProvider.AnalyticsService.AnalyticsEnabledOption = this.usageAnalyticsCheckBox.Checked; @@ -583,5 +576,38 @@ private void CheckAgainLinkLabel_LinkClicked(object sender, LinkLabelLinkClicked { _ = this.StartSastEnablementCheckLoopAsync(); } + + private void OrganizationInfoLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + this.OrganizationInfoLink.LinkVisited = true; + Process.Start("https://docs.snyk.io/ide-tools/visual-studio-extension#organization-setting"); + } + + private void ManageBinariesAutomaticallyCheckbox_CheckedChanged(object sender, EventArgs e) + { + this.OptionsDialogPage.BinariesAutoUpdate = this.ManageBinariesAutomaticallyCheckbox.Checked; + } + + private void CliPathBrowseButton_Click(object sender, EventArgs e) + { + if (this.customCliPathFileDialog.ShowDialog() == DialogResult.OK) + { + var selectedCliPath = this.customCliPathFileDialog.FileName; + this.SetCliCustomPathValue(selectedCliPath); + } + } + + private void SetCliCustomPathValue(string selectedCliPath) + { + this.OptionsDialogPage.CliCustomPath = selectedCliPath; + this.CliPathTextBox.Text = string.IsNullOrEmpty(this.OptionsDialogPage.CliCustomPath) + ? SnykCli.GetSnykCliDefaultPath() + : selectedCliPath; + } + + private void ClearCliCustomPathButton_Click(object sender, EventArgs e) + { + this.SetCliCustomPathValue(string.Empty); + } } } diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx index 0eb1aa7c5..df44b88d8 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykGeneralSettingsUserControl.resx @@ -123,11 +123,20 @@ True + + True + + + True + + + True + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw + vAAADrwBlbxySQAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw 5OV8tprUCSLSsZQSlUkphpjFoGj3V6i4VeZ0vsswTrI5XDpiBg072O0CqnneHa9PRsv96YaQmSEMyBDs MmHnNSTTHPFpdgkkPK0eJyIgeWd+R3jgRYC7ABKeBsL7JwEvL5HwtPYSf/uNtQvZayFh512RVEfAUmWv jZZtldeAJuTjx7SYAQYQKn7xnCU8ABJO2R9gGis+AAAAAElFTkSuQmCC @@ -139,25 +148,28 @@ True + + 463, 17 + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw + vAAADrwBlbxySQAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw 5OV8tprUCSLSsZQSlUkphpjFoGj3V6i4VeZ0vsswTrI5XDpiBg072O0CqnneHa9PRsv96YaQmSEMyBDs MmHnNSTTHPFpdgkkPK0eJyIgeWd+R3jgRYC7ABKeBsL7JwEvL5HwtPYSf/uNtQvZayFh512RVEfAUmWv jZZtldeAJuTjx7SYAQYQKn7xnCU8ABJO2R9gGis+AAAAAElFTkSuQmCC - - 463, 17 - True + + 232, 17 + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw + vAAADrwBlbxySQAAALxJREFUOE/dUzEOwjAQyz9Y+FnzAV7Aa/hHpYiFNW+AqYKhYsl6nKu4So5DgNiw 5OV8tprUCSLSsZQSlUkphpjFoGj3V6i4VeZ0vsswTrI5XDpiBg072O0CqnneHa9PRsv96YaQmSEMyBDs MmHnNSTTHPFpdgkkPK0eJyIgeWd+R3jgRYC7ABKeBsL7JwEvL5HwtPYSf/uNtQvZayFh512RVEfAUmWv jZZtldeAJuTjx7SYAQYQKn7xnCU8ABJO2R9gGis+AAAAAElFTkSuQmCC @@ -166,4 +178,10 @@ 232, 17 + + 463, 17 + + + 1056, 17 + \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs index 595218619..2518a2461 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs @@ -13,19 +13,12 @@ public class SnykSettings /// public SnykSettings() { - this.SolutionSettingsDict = new Dictionary(); - - this.UsageAnalyticsEnabled = true; - - this.OssEnabled = true; - this.SnykCodeQualityEnabled = true; - this.SnykCodeSecurityEnabled = true; } /// /// Gets or sets a value indicating whether usage analytics enabled. /// - public bool UsageAnalyticsEnabled { get; set; } + public bool UsageAnalyticsEnabled { get; set; } = true; /// /// Gets or sets current Cli version. @@ -45,21 +38,31 @@ public SnykSettings() /// /// Gets or sets solution settings dictionary. /// - public IDictionary SolutionSettingsDict { get; set; } + public IDictionary SolutionSettingsDict { get; set; } = new Dictionary(); /// /// Gets or sets a value indicating whether snyk code security enabled. /// - public bool SnykCodeSecurityEnabled { get; set; } + public bool SnykCodeSecurityEnabled { get; set; } = true; /// /// Gets or sets a value indicating whether snyk code quality enabled. /// - public bool SnykCodeQualityEnabled { get; set; } + public bool SnykCodeQualityEnabled { get; set; } = true; /// /// Gets or sets a value indicating whether oss enabled. /// - public bool OssEnabled { get; set; } + public bool OssEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether binaries auto update is enabled. + /// + public bool BinariesAutoUpdateEnabled { get; set; } = true; + + /// + /// Gets or sets the value of the custom CLI path + /// + public string CustomCliPath { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsDialogPage.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsDialogPage.cs index daa8ea482..c18d7f902 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsDialogPage.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsDialogPage.cs @@ -1,6 +1,5 @@ namespace Snyk.VisualStudio.Extension.Shared.Settings { - using System; using System.Runtime.InteropServices; using System.Windows.Forms; using Microsoft.VisualStudio.Shell; diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsUserControl.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsUserControl.cs index 2576dcda5..33cb19e16 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsUserControl.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSolutionOptionsUserControl.cs @@ -6,7 +6,6 @@ using Serilog; using Snyk.Common; using Snyk.VisualStudio.Extension.Shared.Service; - using Task = System.Threading.Tasks.Task; /// /// Solution settings control. diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs index f2fc1e17b..5885dacea 100644 --- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs +++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs @@ -29,6 +29,28 @@ public SnykUserStorageSettingsService(string settingsPath, ISnykServiceProvider this.settingsLoader = new SnykSettingsLoader(settingsPath); } + public bool BinariesAutoUpdate + { + get => this.LoadSettings().BinariesAutoUpdateEnabled; + set + { + var settings = this.LoadSettings(); + settings.BinariesAutoUpdateEnabled = value; + this.settingsLoader.Save(settings); + } + } + + public string CliCustomPath + { + get => this.LoadSettings().CustomCliPath; + set + { + var settings = this.LoadSettings(); + settings.CustomCliPath = value; + this.settingsLoader.Save(settings); + } + } + /// /// Get CLI additional options string. /// diff --git a/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems b/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems index b6fa98c2f..6119770da 100644 --- a/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems +++ b/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems @@ -37,6 +37,30 @@ + + + Component + + + UserControl + + + SnykGeneralSettingsUserControl.cs + + + + + + Component + + + UserControl + + + SnykSolutionOptionsUserControl.cs + + + SnykIcons.resx True @@ -130,40 +154,6 @@ Never - - Never - - - Component - Never - - - UserControl - Never - - - SnykGeneralSettingsUserControl.cs - - - Component - Never - - - UserControl - Never - - - SnykSolutionOptionsUserControl.cs - - - - - Never - - - - Never - Never @@ -532,16 +522,16 @@ Always - - SnykIcons.Designer.cs - ResXFileCodeGenerator - SnykGeneralSettingsUserControl.cs SnykSolutionOptionsUserControl.cs + + SnykIcons.Designer.cs + ResXFileCodeGenerator + VSPackage.Designer.cs ResXFileCodeGenerator diff --git a/Snyk.VisualStudio.Extension.Shared/SnykVSPackage.cs b/Snyk.VisualStudio.Extension.Shared/SnykVSPackage.cs index fbb3ff186..ef5ef4f5c 100644 --- a/Snyk.VisualStudio.Extension.Shared/SnykVSPackage.cs +++ b/Snyk.VisualStudio.Extension.Shared/SnykVSPackage.cs @@ -5,10 +5,8 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; - using System.Windows; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; - using Microsoft.VisualStudio.Threading; using Serilog; using Snyk.Common; using Snyk.VisualStudio.Extension.Shared.Commands; diff --git a/Snyk.VisualStudio.Extension.Shared/Theme/SnykVsThemeService.cs b/Snyk.VisualStudio.Extension.Shared/Theme/SnykVsThemeService.cs index 028fd4a04..1408ed37e 100644 --- a/Snyk.VisualStudio.Extension.Shared/Theme/SnykVsThemeService.cs +++ b/Snyk.VisualStudio.Extension.Shared/Theme/SnykVsThemeService.cs @@ -2,7 +2,6 @@ { using System; using System.Runtime.InteropServices; - using System.Threading.Tasks; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/NotificationService.cs b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/NotificationService.cs index 65413cecf..d3cba0c71 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/NotificationService.cs +++ b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/NotificationService.cs @@ -1,7 +1,6 @@ namespace Snyk.VisualStudio.Extension.Shared.UI.Notifications { using Snyk.VisualStudio.Extension.Shared.Service; - using System; /// /// Snyk VS notification service. diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBar.cs b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBar.cs index dd642fa80..200b08c20 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBar.cs +++ b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBar.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Snyk.VisualStudio.Extension.Shared.Service; + using Task = System.Threading.Tasks.Task; /// /// Wrapper for Visual Studio status bar. @@ -38,11 +39,11 @@ public class VsStatusBar /// Message box title. /// Message box message. /// Task - public System.Threading.Tasks.Task ShowMessageBoxAsync(string title, string message) + public Task ShowMessageBoxAsync(string title, string message) { MessageBox.Show(message, title); - return System.Threading.Tasks.Task.CompletedTask; + return Task.CompletedTask; } /// @@ -79,7 +80,7 @@ public void ShowSnykCodeUpdateMessage(string message) _ = this.ShowMessageWithProgressIconAsync(message, (short)Constants.SBAI_Build, 0); } - private async System.Threading.Tasks.Task ShowMessageWithProgressIconAsync(string message, object icon, int showIcon) + private async Task ShowMessageWithProgressIconAsync(string message, object icon, int showIcon) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -89,7 +90,7 @@ private async System.Threading.Tasks.Task ShowMessageWithProgressIconAsync(strin statusBar.Animation(showIcon, ref icon); } - private async System.Threading.Tasks.Task ShowMessageAsync(string message) + private async Task ShowMessageAsync(string message) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBarNotificationService.cs b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBarNotificationService.cs index e3cf8a857..ee8981e48 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBarNotificationService.cs +++ b/Snyk.VisualStudio.Extension.Shared/UI/Notifications/VsStatusBarNotificationService.cs @@ -1,5 +1,6 @@ namespace Snyk.VisualStudio.Extension.Shared.UI.Notifications { + using System; using Snyk.Code.Library.Service; using Snyk.VisualStudio.Extension.Shared.Service; using Snyk.VisualStudio.Extension.Shared.Settings; @@ -48,6 +49,7 @@ public void InitializeEventListeners(ISnykServiceProvider serviceProvider) tasksService.DownloadStarted += this.OnDownloadStarted; tasksService.DownloadFinished += this.OnDownloadFinished; tasksService.DownloadCancelled += this.OnDownloadCancelled; + tasksService.DownloadFailed += this.OnDownloadFailed; tasksService.ScanningCancelled += this.OnScanningCancelled; tasksService.CliScanningStarted += this.OnCliScanningStarted; @@ -127,5 +129,8 @@ private void OnDownloadStarted(object sender, SnykCliDownloadEventArgs eventArgs private void OnDownloadCancelled(object sender, SnykCliDownloadEventArgs eventArgs) => this.statusBar.ShowDownloadFinishedMessage("Snyk CLI download cancelled"); + + private void OnDownloadFailed(object sender, Exception exception) + => this.statusBar.ShowDownloadFinishedMessage("Snyk CLI download failed"); } } diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml index 0a3ba476c..0c5fd76f4 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml +++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml @@ -9,6 +9,9 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + pack://application:,,,/Snyk.VisualStudio.Extension;component/SnykDogLogoFullSize.png + Select an issue and start improving your project. @@ -31,11 +34,11 @@ - + - + @@ -60,7 +63,7 @@ Test code now - + By connecting your account with Snyk, you agree @@ -77,7 +80,7 @@ - + Snyk Code is configured to use a Local Code Engine instance. This setup is not yet supported by the extension. diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs index 31027a7ce..97e8273e5 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs +++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs @@ -1,20 +1,21 @@ namespace Snyk.VisualStudio.Extension.Shared.UI.Toolwindow { - using System; using System.Collections.Generic; using System.Diagnostics; + using System.IO; + using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using Microsoft.VisualStudio.Shell; + using Microsoft.VisualStudio.Threading; using Snyk.VisualStudio.Extension.Shared.Service; - using Snyk.VisualStudio.Extension.Shared.UI.Notifications; /// /// Interaction logic for MessagePanel.xaml. /// public partial class MessagePanel : UserControl { - private IList panels; + private readonly IList panels; /// /// Initializes a new instance of the class. @@ -31,7 +32,7 @@ public MessagePanel() this.messagePanel, this.overviewPanel, this.scanningProjectMessagePanel, - this.localCodeEngineIsEnabledPanel, + this.localCodeEngineIsDisabledPanel, }; } @@ -46,7 +47,7 @@ public MessagePanel() public ToolWindowContext Context { get; set; } /// - /// Sets text on panel. + /// Sets text on the and shows it. /// public string Text { @@ -69,9 +70,9 @@ public string Text public void ShowSelectIssueMessage() => this.ShowPanel(this.selectIssueMessagePanel); /// - /// + /// Shows the "local code engine is disabled" message. /// - public void ShowDisabledDueToLocalCodeEngineMessage() => this.ShowPanel(this.localCodeEngineIsEnabledPanel); + public void ShowDisabledDueToLocalCodeEngineMessage() => this.ShowPanel(this.localCodeEngineIsDisabledPanel); /// /// Show scanning message. @@ -95,50 +96,32 @@ private void ShowPanel(StackPanel panel) panel.Visibility = Visibility.Visible; } - private void TestCodeNow_Click(object sender, RoutedEventArgs e) + private async void TestCodeNow_Click(object sender, RoutedEventArgs e) { - Action successCallbackAction = (apiToken) => - { - ThreadHelper.JoinableTaskFactory.RunAsync(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - this.testCodeNowButton.IsEnabled = true; - - this.connectVSToSnykProgressBar.Visibility = Visibility.Collapsed; - }); - - this.ServiceProvider.Options.ApiToken = apiToken; + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + this.testCodeNowButton.IsEnabled = false; + this.authenticateSnykProgressBar.Visibility = Visibility.Visible; - this.Context.TransitionTo(RunScanState.Instance); - }; - - Action errorCallbackAction = (error) => + await TaskScheduler.Default; + bool authenticationSucceeded; + try { - ThreadHelper.JoinableTaskFactory.RunAsync(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - this.testCodeNowButton.IsEnabled = true; - - this.connectVSToSnykProgressBar.Visibility = Visibility.Collapsed; - - NotificationService.Instance.ShowErrorInfoBar(error); - }); - - this.Context.TransitionTo(OverviewState.Instance); - }; - - ThreadHelper.JoinableTaskFactory.RunAsync(async () => + authenticationSucceeded = this.ServiceProvider.Options.Authenticate(); + } + catch (FileNotFoundException) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + this.authenticateSnykProgressBar.Visibility = Visibility.Collapsed; + this.Text = "Snyk CLI not found. You can specify a path to a Snyk CLI executable from the settings."; + return; + } - this.testCodeNowButton.IsEnabled = false; - - this.connectVSToSnykProgressBar.Visibility = Visibility.Visible; - }); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + this.authenticateSnykProgressBar.Visibility = Visibility.Collapsed; + this.testCodeNowButton.IsEnabled = true; - this.ServiceProvider.Options.Authenticate(successCallbackAction, errorCallbackAction); + var nextPanel = authenticationSucceeded ? (ToolWindowState)RunScanState.Instance : OverviewState.Instance; + this.Context.TransitionTo(nextPanel); } private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs args) diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs index 5fa2c8fa4..7e29f0226 100644 --- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs +++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/SnykToolWindowControl.xaml.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; using System.Threading; - using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; @@ -104,8 +103,9 @@ public void InitializeEventListeners(ISnykServiceProvider serviceProvider) tasksService.DownloadFinished += this.OnDownloadFinished; tasksService.DownloadUpdate += (sender, args) => ThreadHelper.JoinableTaskFactory.RunAsync(() => this.OnDownloadUpdateAsync(sender, args)); tasksService.DownloadCancelled += this.OnDownloadCancelled; + tasksService.DownloadFailed += this.OnDownloadFailed; - this.Loaded += tasksService.OnUiLoaded; + this.Loaded += (sender, args) => tasksService.Download(); serviceProvider.VsThemeService.ThemeChanged += this.OnVsThemeChanged; @@ -312,7 +312,32 @@ public void OnDownloadStarted(object sender, SnykCliDownloadEventArgs eventArgs) /// /// Source object. /// Event args. - public void OnDownloadCancelled(object sender, SnykCliDownloadEventArgs eventArgs) => this.ShowWelcomeOrRunScanScreen(); + public void OnDownloadCancelled(object sender, SnykCliDownloadEventArgs eventArgs) + { + var snykCli = this.serviceProvider.NewCli(); + if (snykCli.IsCliFileFound()) + { + this.ShowWelcomeOrRunScanScreen(); + } + else + { + this.messagePanel.Text = "Snyk CLI not found. You can specify a path to a Snyk CLI executable from the settings."; + } + } + + private void OnDownloadFailed(object sender, Exception e) + { + var snykCli = this.serviceProvider.NewCli(); + if (snykCli.IsCliFileFound()) + { + this.ShowWelcomeOrRunScanScreen(); + } + else + { + this.messagePanel.Text = + "Failed to download Snyk CLI. You can specify a path to a Snyk CLI executable from the settings."; + } + } /// /// VsThemeChanged event handler. Call Adapt methods for controls. diff --git a/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs b/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs index dadd8cef4..e7139aa01 100644 --- a/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs +++ b/Snyk.VisualStudio.Extension.Tests/SnykCliTest.cs @@ -25,9 +25,8 @@ public SnykCliTest() [Fact] public void SnykCliTest_CliReturnError_GetApiTokenThrowException() { - var cli = new SnykCli + var cli = new SnykCli(this.optionsMock.Object) { - Options = this.optionsMock.Object, ConsoleRunner = new SnykMockConsoleRunner("cli file note exists"), }; @@ -37,11 +36,6 @@ public void SnykCliTest_CliReturnError_GetApiTokenThrowException() [Fact] public void SnykCliTest_ConvertRawCliStringToCliResultWithCriticalSeverity_CriticalSeverityVulnExists() { - var cli = new SnykCli - { - Options = this.optionsMock.Object, - }; - var cliResult = SnykCli.ConvertRawCliStringToCliResult(this.GetFileContent("CriticalSeverityObject.json")); bool isCriticalSeverityVulnExists = false; @@ -60,9 +54,9 @@ public void SnykCliTest_ConvertRawCliStringToCliResultWithCriticalSeverity_Criti [Fact] public async Task SnykCliTest_RunScan_SuccessfulCliResultAsync() { - var cli = new SnykCli + var cli = new SnykCli(this.optionsMock.Object) { - Options = this.optionsMock.Object, + ConsoleRunner = new SnykMockConsoleRunner(this.GetFileContent("VulnerabilitiesSingleObject.json")), }; @@ -76,9 +70,9 @@ public void SnykCliTest_GetApiToken_Successful() { string testGuid = Guid.NewGuid().ToString(); - var cli = new SnykCli + var cli = new SnykCli(this.optionsMock.Object) { - Options = this.optionsMock.Object, + ConsoleRunner = new SnykMockConsoleRunner(testGuid), }; @@ -88,9 +82,9 @@ public void SnykCliTest_GetApiToken_Successful() [Fact] public void SnykCliTest_Authenticate_Successful() { - var cli = new SnykCli + var cli = new SnykCli(this.optionsMock.Object) { - Options = this.optionsMock.Object, + ConsoleRunner = new SnykMockConsoleRunner("Your account has been authenticated. Snyk is now ready to be used."), }; @@ -100,10 +94,7 @@ public void SnykCliTest_Authenticate_Successful() [Fact] public async Task SnykCliTest_BuildArguments_WithoutOptionsAsync() { - var cli = new SnykCli - { - Options = this.optionsMock.Object, - }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test", await cli.BuildScanArgumentsAsync()); } @@ -115,7 +106,7 @@ public async Task SnykCliTest_BuildArguments_WithCustomEndpointOptionAsync() .Setup(options => options.CustomEndpoint) .Returns("https://github.com/snyk/"); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --API=https://github.com/snyk/", await cli.BuildScanArgumentsAsync()); } @@ -127,7 +118,7 @@ public async Task SnykCliTest_BuildArguments_WithInsecureOptionAsync() .Setup(options => options.IgnoreUnknownCA) .Returns(true); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --insecure", await cli.BuildScanArgumentsAsync()); } @@ -139,7 +130,7 @@ public async Task SnykCliTest_BuildArguments_WithOrganizationOptionAsync() .Setup(options => options.Organization) .Returns("test-snyk-organization"); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --org=test-snyk-organization", await cli.BuildScanArgumentsAsync()); } @@ -151,7 +142,7 @@ public async Task SnykCliTest_BuildArguments_WithAdditionalOptionsAsync() .Setup(options => options.GetAdditionalOptionsAsync()) .ReturnsAsync("--file=C:\build.pom"); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --file=C:\build.pom", await cli.BuildScanArgumentsAsync()); } @@ -163,7 +154,7 @@ public async Task SnykCliTest_BuildArguments_WithScanAllProjectsAsync() .Setup(options => options.IsScanAllProjectsAsync()) .ReturnsAsync(true); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --all-projects", await cli.BuildScanArgumentsAsync()); } @@ -195,7 +186,7 @@ public async Task SnykCliTest_BuildArguments_WithAllOptionsAsync() .Setup(options => options.IsScanAllProjectsAsync()) .ReturnsAsync(true); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal( "--json test --API=https://github.com/snyk/ --insecure --org=test-snyk-organization --ignore-policy --all-projects --DISABLE_ANALYTICS", @@ -209,7 +200,7 @@ public async Task SnykCliTest_BuildArguments_WithDisableAnalyticsAsync() .Setup(options => options.UsageAnalyticsEnabled) .Returns(false); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); Assert.Equal("--json test --DISABLE_ANALYTICS", await cli.BuildScanArgumentsAsync()); } @@ -245,7 +236,7 @@ public void SnykCliTest_BuildEnvironmentVariables_WithAllOptions() .Setup(options => options.ApiToken) .Returns("test-token"); - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); var result = cli.BuildScanEnvironmentVariables(); @@ -295,7 +286,7 @@ public void ConvertRawCliStringToCliResult_ErrorJson() [Fact] public void SnykCliTest_ConvertRawCliStringToCliResult_PlainTextError() { - var cli = new SnykCli { Options = this.optionsMock.Object, }; + var cli = new SnykCli(this.optionsMock.Object); var cliResult = SnykCli.ConvertRawCliStringToCliResult(this.GetFileContent("ErrorPlainText.json")); diff --git a/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj b/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj index 53f114eb0..014717338 100644 --- a/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj +++ b/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj @@ -26,6 +26,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp + $(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile true @@ -147,4 +148,26 @@ + + + + + + + + + \ No newline at end of file