Skip to content

Commit

Permalink
feat: add workspace trust (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
michelkaporin authored Nov 29, 2022
1 parent 94d17c0 commit 0b53dbb
Show file tree
Hide file tree
Showing 19 changed files with 638 additions and 38 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Snyk Changelog

## [1.1.31]

### Added
- Adds workspace trust mechanism to ensure scans are run on the trusted projects.

## [1.1.30]

### Changed
Expand Down Expand Up @@ -48,7 +53,7 @@

### Added
- Organization description information in settings.

### Fixed
- Changing custom endpoint settings leads to authentication errors.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TrustDialogWindow.xaml.cs">
<DependentUpon>TrustDialogWindow.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="source.extension.vsixmanifest">
Expand Down Expand Up @@ -120,6 +123,12 @@
<Name>Snyk.Common</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Page Include="TrustDialogWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Import Project="..\Snyk.VisualStudio.Extension.Shared\Snyk.VisualStudio.Extension.Shared.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
Expand Down
59 changes: 59 additions & 0 deletions Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<ui:DialogWindow x:Class="Snyk.VisualStudio.Extension.TrustDialogWindow"
x:Name="TrustWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
IsCloseButtonEnabled="True"
HasHelpButton="False"
MinHeight="290" Height="290"
MinWidth="500" Width="500"
BorderBrush="{x:Static SystemColors.WindowFrameBrush}" BorderThickness="1"
WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True"
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
toolkit:Themes.UseVsTheme="True"
Title="Snyk - This folder has not been trusted"
MouseDown="TrustDialogWindow_OnMouseDown">
<DockPanel Margin="10">
<Button DockPanel.Dock="Top" HorizontalAlignment="Right" Click="DoNotTrustButton_OnClick" MinWidth="1" MinHeight="1" Width="35" Margin="0" Padding="0">
<imaging:CrispImage Moniker="{x:Static catalog:KnownMonikers.Close}"/>
</Button>
<StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button x:Name="TrustButton" Margin="5, 5" Content="Trust folder and continue" Click="TrustButton_OnClick"/>
<Button x:Name="DoNotTrustButton" Margin="5, 5" Content="Don't scan" Click="DoNotTrustButton_OnClick"/>
</StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<imaging:CrispImage Grid.Column="0" Width="50" Moniker="{x:Static catalog:KnownMonikers.StatusSecurityWarning}"/>
<StackPanel VerticalAlignment="Center" Grid.Column="1" Margin="0, 0, 5, 0">
<TextBlock FontSize="14">This folder has not been trusted:</TextBlock>
<TextBlock FontSize="14" FontWeight="Bold" TextWrapping="Wrap" Text="{Binding ElementName=TrustWindow, Path=FolderPath}"/>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1" Margin="5">
<TextBlock TextWrapping="Wrap">
When scanning folder files for vulnerabilities, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan folders you trust.
</TextBlock>
<TextBlock>
<LineBreak/>
<Hyperlink NavigateUri="https://docs.snyk.io/ide-tools/visual-studio-extension/workspace-trust" RequestNavigate="Hyperlink_OnRequestNavigate">
More information
</Hyperlink>
</TextBlock>
</StackPanel>
</Grid>
</DockPanel>
</ui:DialogWindow>
49 changes: 49 additions & 0 deletions Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

namespace Snyk.VisualStudio.Extension
{
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using Microsoft.VisualStudio.PlatformUI;

/// <summary>
/// Trusted dialog window for Visual Studio 2022.
/// </summary>
public partial class TrustDialogWindow : DialogWindow
{
public TrustDialogWindow(string folderPath)
{
this.FolderPath = folderPath;
this.InitializeComponent();
}

public string FolderPath { get; }

private void DoNotTrustButton_OnClick(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}

private void TrustButton_OnClick(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
this.Close();
}

private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}

private void TrustDialogWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.DragMove();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public interface ISnykServiceProvider
/// </summary>
ISolutionService SolutionService { get; }

IWorkspaceTrustService WorkspaceTrustService { get; }

/// <summary>
/// Gets Tasks service instance.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Snyk.VisualStudio.Extension.Shared.Service
{
public interface IWorkspaceTrustService
{
bool IsFolderTrusted(string absoluteFolderPath);

void AddFolderToTrusted(string absoluteFolderPath);
}
}
8 changes: 8 additions & 0 deletions Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class SnykService : ISnykServiceProvider, ISnykService

private ISentryService sentryService;

private IWorkspaceTrustService workspaceTrustService;

/// <summary>
/// Initializes a new instance of the <see cref="SnykService"/> class.
/// </summary>
Expand All @@ -68,6 +70,11 @@ public class SnykService : ISnykServiceProvider, ISnykService
/// </summary>
public ISolutionService SolutionService => SnykSolutionService.Instance;

/// <summary>
/// Gets solution service.
/// </summary>
public IWorkspaceTrustService WorkspaceTrustService => this.workspaceTrustService;

/// <summary>
/// Gets Tasks service.
/// </summary>
Expand Down Expand Up @@ -241,6 +248,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken)
this.dte = await this.serviceProvider.GetServiceAsync(typeof(DTE)) as DTE2;
await SnykSolutionService.Instance.InitializeAsync(this);
this.tasksService = SnykTasksService.Instance;
this.workspaceTrustService = new WorkspaceTrustService(this.UserStorageSettingsService);

NotificationService.Initialize(this);
VsStatusBar.Initialize(this);
Expand Down
46 changes: 45 additions & 1 deletion Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Serilog;
using Snyk.Analytics;
using Snyk.Code.Library.Domain.Analysis;
using Snyk.Common;
using Snyk.VisualStudio.Extension.Shared.CLI;
using Snyk.VisualStudio.Extension.Shared.CLI.Download;
using Snyk.VisualStudio.Extension.Shared.Service.Domain;
using Snyk.VisualStudio.Extension.Shared.UI;
using static Snyk.VisualStudio.Extension.Shared.CLI.Download.SnykCliDownloader;
using Task = System.Threading.Tasks.Task;

Expand Down Expand Up @@ -198,7 +201,6 @@ public void CancelTasks()
public async Task ScanAsync()
{
Logger.Information("Enter Scan method");

try
{
var selectedFeatures = await this.GetFeaturesSettingsAsync();
Expand All @@ -212,6 +214,13 @@ public async Task ScanAsync()
return;
}

var isFolderTrusted = await this.IsFolderTrustedAsync();
if (!isFolderTrusted)
{
Logger.Information("Workspace folder was not trusted for scanning.");
return;
}

this.serviceProvider.AnalyticsService.LogAnalysisIsTriggeredEvent(this.GetSelectedFeatures(selectedFeatures));

var ossScanTask = this.ScanOssAsync(selectedFeatures);
Expand All @@ -225,6 +234,41 @@ public async Task ScanAsync()
}
}

/// <summary>
/// Checks if opened solution folder is trusted. If not, prompts a user with trust permission.
/// </summary>
/// <returns>Folder is trusted or not.</returns>
public async Task<bool> IsFolderTrustedAsync()
{
var solutionFolderPath = await this.serviceProvider.SolutionService.GetSolutionFolderAsync();
var isFolderTrusted = this.serviceProvider.WorkspaceTrustService.IsFolderTrusted(solutionFolderPath);

if (string.IsNullOrEmpty(solutionFolderPath) || isFolderTrusted)
{
return true;
}

var trustDialog = new TrustDialogWindow(solutionFolderPath);
var trusted = trustDialog.ShowModal();

if (trusted != true)
{
return false;
}

try
{
this.serviceProvider.WorkspaceTrustService.AddFolderToTrusted(solutionFolderPath);
Logger.Information("Workspace folder was trusted: {SolutionFolderPath}", solutionFolderPath);
return true;
}
catch (ArgumentException e)
{
Logger.Error(e, "Failed to add folder to trusted list.");
throw;
}
}

/// <summary>
/// Start a CLI download task in background thread.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
namespace Snyk.VisualStudio.Extension.Shared.Service
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Serilog;
using Snyk.Common;
using Snyk.VisualStudio.Extension.Shared.Settings;

public class WorkspaceTrustService : IWorkspaceTrustService
{
private static readonly ILogger Logger = LogManager.ForContext<WorkspaceTrustService>();

private readonly IUserStorageSettingsService settingsService;

public WorkspaceTrustService(IUserStorageSettingsService settingsService)
{
this.settingsService = settingsService;
}

public void AddFolderToTrusted(string absoluteFolderPath)
{
if (!Path.IsPathRooted(absoluteFolderPath))
{
throw new ArgumentException("Trusted folder path provided is not absolute.");
}

if (!Directory.Exists(absoluteFolderPath))
{
throw new ArgumentException("Trusted folder doesn't exist.");
}

try
{
var trustedFolders = this.settingsService.TrustedFolders;
trustedFolders.Add(absoluteFolderPath);
this.settingsService.TrustedFolders = trustedFolders;
}
catch (Exception e)
{
Logger.Error(e, "Failed to add a folder to trusted.");
}
}

public bool IsFolderTrusted(string absoluteFolderPath)
{
var trustedFolders = this.settingsService.TrustedFolders;

foreach (var trustedFolder in trustedFolders)
{
if (this.IsSubFolderOrEqual(trustedFolder, absoluteFolderPath))
{
return true;
}
}

return false;
}

/// <summary>
/// Verify if subfolder is rooted at parent path.
/// </summary>
/// <param name="parentPath">Parent path to check against.</param>
/// <param name="childPath">Subfolder path to verify.</param>
/// <returns>Returns true if childPath is subfolder of parentPath, or equal to it.</returns>
private bool IsSubFolderOrEqual(string parentPath, string childPath)
{
var parentUri = new Uri(parentPath);
if (new Uri(childPath).Equals(parentUri))
{
return true;
}

var childUri = new DirectoryInfo(childPath).Parent;
while (childUri != null)
{
if (new Uri(childUri.FullName).Equals(parentUri))
{
return true;
}

childUri = childUri.Parent;
}

return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Snyk.VisualStudio.Extension.Shared.Settings
{
using System.Collections.Generic;

public interface IUserStorageSettingsService
{
ISet<string> TrustedFolders { get; set; }
}
}
7 changes: 6 additions & 1 deletion Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ public SnykSettings()
public bool BinariesAutoUpdateEnabled { get; set; } = true;

/// <summary>
/// Gets or sets the value of the custom CLI path
/// Gets or sets the value of the custom CLI path.
/// </summary>
public string CustomCliPath { get; set; } = string.Empty;

/// <summary>
/// Gets or sets an array of workspace trusted folders.
/// </summary>
public ISet<string> TrustedFolders { get; set; } = new HashSet<string>();
}
}
Loading

0 comments on commit 0b53dbb

Please sign in to comment.