Skip to content

Commit 0b53dbb

Browse files
feat: add workspace trust (#217)
1 parent 94d17c0 commit 0b53dbb

19 files changed

Lines changed: 638 additions & 38 deletions

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Snyk Changelog
22

3+
## [1.1.31]
4+
5+
### Added
6+
- Adds workspace trust mechanism to ensure scans are run on the trusted projects.
7+
38
## [1.1.30]
49

510
### Changed
@@ -48,7 +53,7 @@
4853

4954
### Added
5055
- Organization description information in settings.
51-
56+
5257
### Fixed
5358
- Changing custom endpoint settings leads to authentication errors.
5459

Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
</PropertyGroup>
5050
<ItemGroup>
5151
<Compile Include="Properties\AssemblyInfo.cs" />
52+
<Compile Include="TrustDialogWindow.xaml.cs">
53+
<DependentUpon>TrustDialogWindow.xaml</DependentUpon>
54+
</Compile>
5255
</ItemGroup>
5356
<ItemGroup>
5457
<None Include="source.extension.vsixmanifest">
@@ -120,6 +123,12 @@
120123
<Name>Snyk.Common</Name>
121124
</ProjectReference>
122125
</ItemGroup>
126+
<ItemGroup>
127+
<Page Include="TrustDialogWindow.xaml">
128+
<Generator>MSBuild:Compile</Generator>
129+
<SubType>Designer</SubType>
130+
</Page>
131+
</ItemGroup>
123132
<Import Project="..\Snyk.VisualStudio.Extension.Shared\Snyk.VisualStudio.Extension.Shared.projitems" Label="Shared" />
124133
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
125134
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<ui:DialogWindow x:Class="Snyk.VisualStudio.Extension.TrustDialogWindow"
2+
x:Name="TrustWindow"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
8+
xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
9+
mc:Ignorable="d"
10+
WindowStartupLocation="CenterScreen"
11+
IsCloseButtonEnabled="True"
12+
HasHelpButton="False"
13+
MinHeight="290" Height="290"
14+
MinWidth="500" Width="500"
15+
BorderBrush="{x:Static SystemColors.WindowFrameBrush}" BorderThickness="1"
16+
WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True"
17+
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
18+
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
19+
toolkit:Themes.UseVsTheme="True"
20+
Title="Snyk - This folder has not been trusted"
21+
MouseDown="TrustDialogWindow_OnMouseDown">
22+
<DockPanel Margin="10">
23+
<Button DockPanel.Dock="Top" HorizontalAlignment="Right" Click="DoNotTrustButton_OnClick" MinWidth="1" MinHeight="1" Width="35" Margin="0" Padding="0">
24+
<imaging:CrispImage Moniker="{x:Static catalog:KnownMonikers.Close}"/>
25+
</Button>
26+
<StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal">
27+
<Button x:Name="TrustButton" Margin="5, 5" Content="Trust folder and continue" Click="TrustButton_OnClick"/>
28+
<Button x:Name="DoNotTrustButton" Margin="5, 5" Content="Don't scan" Click="DoNotTrustButton_OnClick"/>
29+
</StackPanel>
30+
<Grid>
31+
<Grid.RowDefinitions>
32+
<RowDefinition Height="auto"/>
33+
<RowDefinition Height="auto"/>
34+
</Grid.RowDefinitions>
35+
<Grid Grid.Row="0">
36+
<Grid.ColumnDefinitions>
37+
<ColumnDefinition Width="*"/>
38+
<ColumnDefinition Width="5*"/>
39+
</Grid.ColumnDefinitions>
40+
<imaging:CrispImage Grid.Column="0" Width="50" Moniker="{x:Static catalog:KnownMonikers.StatusSecurityWarning}"/>
41+
<StackPanel VerticalAlignment="Center" Grid.Column="1" Margin="0, 0, 5, 0">
42+
<TextBlock FontSize="14">This folder has not been trusted:</TextBlock>
43+
<TextBlock FontSize="14" FontWeight="Bold" TextWrapping="Wrap" Text="{Binding ElementName=TrustWindow, Path=FolderPath}"/>
44+
</StackPanel>
45+
</Grid>
46+
<StackPanel Grid.Row="1" Margin="5">
47+
<TextBlock TextWrapping="Wrap">
48+
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.
49+
</TextBlock>
50+
<TextBlock>
51+
<LineBreak/>
52+
<Hyperlink NavigateUri="https://docs.snyk.io/ide-tools/visual-studio-extension/workspace-trust" RequestNavigate="Hyperlink_OnRequestNavigate">
53+
More information
54+
</Hyperlink>
55+
</TextBlock>
56+
</StackPanel>
57+
</Grid>
58+
</DockPanel>
59+
</ui:DialogWindow>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+

2+
namespace Snyk.VisualStudio.Extension
3+
{
4+
using System.Diagnostics;
5+
using System.Windows;
6+
using System.Windows.Input;
7+
using System.Windows.Navigation;
8+
using Microsoft.VisualStudio.PlatformUI;
9+
10+
/// <summary>
11+
/// Trusted dialog window for Visual Studio 2022.
12+
/// </summary>
13+
public partial class TrustDialogWindow : DialogWindow
14+
{
15+
public TrustDialogWindow(string folderPath)
16+
{
17+
this.FolderPath = folderPath;
18+
this.InitializeComponent();
19+
}
20+
21+
public string FolderPath { get; }
22+
23+
private void DoNotTrustButton_OnClick(object sender, RoutedEventArgs e)
24+
{
25+
this.DialogResult = false;
26+
this.Close();
27+
}
28+
29+
private void TrustButton_OnClick(object sender, RoutedEventArgs e)
30+
{
31+
this.DialogResult = true;
32+
this.Close();
33+
}
34+
35+
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
36+
{
37+
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
38+
e.Handled = true;
39+
}
40+
41+
private void TrustDialogWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
42+
{
43+
if (e.ChangedButton == MouseButton.Left)
44+
{
45+
this.DragMove();
46+
}
47+
}
48+
}
49+
}

Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public interface ISnykServiceProvider
3838
/// </summary>
3939
ISolutionService SolutionService { get; }
4040

41+
IWorkspaceTrustService WorkspaceTrustService { get; }
42+
4143
/// <summary>
4244
/// Gets Tasks service instance.
4345
/// </summary>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Snyk.VisualStudio.Extension.Shared.Service
2+
{
3+
public interface IWorkspaceTrustService
4+
{
5+
bool IsFolderTrusted(string absoluteFolderPath);
6+
7+
void AddFolderToTrusted(string absoluteFolderPath);
8+
}
9+
}

Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public class SnykService : ISnykServiceProvider, ISnykService
5252

5353
private ISentryService sentryService;
5454

55+
private IWorkspaceTrustService workspaceTrustService;
56+
5557
/// <summary>
5658
/// Initializes a new instance of the <see cref="SnykService"/> class.
5759
/// </summary>
@@ -68,6 +70,11 @@ public class SnykService : ISnykServiceProvider, ISnykService
6870
/// </summary>
6971
public ISolutionService SolutionService => SnykSolutionService.Instance;
7072

73+
/// <summary>
74+
/// Gets solution service.
75+
/// </summary>
76+
public IWorkspaceTrustService WorkspaceTrustService => this.workspaceTrustService;
77+
7178
/// <summary>
7279
/// Gets Tasks service.
7380
/// </summary>
@@ -241,6 +248,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken)
241248
this.dte = await this.serviceProvider.GetServiceAsync(typeof(DTE)) as DTE2;
242249
await SnykSolutionService.Instance.InitializeAsync(this);
243250
this.tasksService = SnykTasksService.Instance;
251+
this.workspaceTrustService = new WorkspaceTrustService(this.UserStorageSettingsService);
244252

245253
NotificationService.Initialize(this);
246254
VsStatusBar.Initialize(this);

Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
using System.Collections.Generic;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using Community.VisualStudio.Toolkit;
78
using Microsoft.VisualStudio.Shell;
9+
using Microsoft.VisualStudio.Shell.Interop;
810
using Serilog;
911
using Snyk.Analytics;
1012
using Snyk.Code.Library.Domain.Analysis;
1113
using Snyk.Common;
1214
using Snyk.VisualStudio.Extension.Shared.CLI;
1315
using Snyk.VisualStudio.Extension.Shared.CLI.Download;
1416
using Snyk.VisualStudio.Extension.Shared.Service.Domain;
17+
using Snyk.VisualStudio.Extension.Shared.UI;
1518
using static Snyk.VisualStudio.Extension.Shared.CLI.Download.SnykCliDownloader;
1619
using Task = System.Threading.Tasks.Task;
1720

@@ -198,7 +201,6 @@ public void CancelTasks()
198201
public async Task ScanAsync()
199202
{
200203
Logger.Information("Enter Scan method");
201-
202204
try
203205
{
204206
var selectedFeatures = await this.GetFeaturesSettingsAsync();
@@ -212,6 +214,13 @@ public async Task ScanAsync()
212214
return;
213215
}
214216

217+
var isFolderTrusted = await this.IsFolderTrustedAsync();
218+
if (!isFolderTrusted)
219+
{
220+
Logger.Information("Workspace folder was not trusted for scanning.");
221+
return;
222+
}
223+
215224
this.serviceProvider.AnalyticsService.LogAnalysisIsTriggeredEvent(this.GetSelectedFeatures(selectedFeatures));
216225

217226
var ossScanTask = this.ScanOssAsync(selectedFeatures);
@@ -225,6 +234,41 @@ public async Task ScanAsync()
225234
}
226235
}
227236

237+
/// <summary>
238+
/// Checks if opened solution folder is trusted. If not, prompts a user with trust permission.
239+
/// </summary>
240+
/// <returns>Folder is trusted or not.</returns>
241+
public async Task<bool> IsFolderTrustedAsync()
242+
{
243+
var solutionFolderPath = await this.serviceProvider.SolutionService.GetSolutionFolderAsync();
244+
var isFolderTrusted = this.serviceProvider.WorkspaceTrustService.IsFolderTrusted(solutionFolderPath);
245+
246+
if (string.IsNullOrEmpty(solutionFolderPath) || isFolderTrusted)
247+
{
248+
return true;
249+
}
250+
251+
var trustDialog = new TrustDialogWindow(solutionFolderPath);
252+
var trusted = trustDialog.ShowModal();
253+
254+
if (trusted != true)
255+
{
256+
return false;
257+
}
258+
259+
try
260+
{
261+
this.serviceProvider.WorkspaceTrustService.AddFolderToTrusted(solutionFolderPath);
262+
Logger.Information("Workspace folder was trusted: {SolutionFolderPath}", solutionFolderPath);
263+
return true;
264+
}
265+
catch (ArgumentException e)
266+
{
267+
Logger.Error(e, "Failed to add folder to trusted list.");
268+
throw;
269+
}
270+
}
271+
228272
/// <summary>
229273
/// Start a CLI download task in background thread.
230274
/// </summary>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
namespace Snyk.VisualStudio.Extension.Shared.Service
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using Serilog;
8+
using Snyk.Common;
9+
using Snyk.VisualStudio.Extension.Shared.Settings;
10+
11+
public class WorkspaceTrustService : IWorkspaceTrustService
12+
{
13+
private static readonly ILogger Logger = LogManager.ForContext<WorkspaceTrustService>();
14+
15+
private readonly IUserStorageSettingsService settingsService;
16+
17+
public WorkspaceTrustService(IUserStorageSettingsService settingsService)
18+
{
19+
this.settingsService = settingsService;
20+
}
21+
22+
public void AddFolderToTrusted(string absoluteFolderPath)
23+
{
24+
if (!Path.IsPathRooted(absoluteFolderPath))
25+
{
26+
throw new ArgumentException("Trusted folder path provided is not absolute.");
27+
}
28+
29+
if (!Directory.Exists(absoluteFolderPath))
30+
{
31+
throw new ArgumentException("Trusted folder doesn't exist.");
32+
}
33+
34+
try
35+
{
36+
var trustedFolders = this.settingsService.TrustedFolders;
37+
trustedFolders.Add(absoluteFolderPath);
38+
this.settingsService.TrustedFolders = trustedFolders;
39+
}
40+
catch (Exception e)
41+
{
42+
Logger.Error(e, "Failed to add a folder to trusted.");
43+
}
44+
}
45+
46+
public bool IsFolderTrusted(string absoluteFolderPath)
47+
{
48+
var trustedFolders = this.settingsService.TrustedFolders;
49+
50+
foreach (var trustedFolder in trustedFolders)
51+
{
52+
if (this.IsSubFolderOrEqual(trustedFolder, absoluteFolderPath))
53+
{
54+
return true;
55+
}
56+
}
57+
58+
return false;
59+
}
60+
61+
/// <summary>
62+
/// Verify if subfolder is rooted at parent path.
63+
/// </summary>
64+
/// <param name="parentPath">Parent path to check against.</param>
65+
/// <param name="childPath">Subfolder path to verify.</param>
66+
/// <returns>Returns true if childPath is subfolder of parentPath, or equal to it.</returns>
67+
private bool IsSubFolderOrEqual(string parentPath, string childPath)
68+
{
69+
var parentUri = new Uri(parentPath);
70+
if (new Uri(childPath).Equals(parentUri))
71+
{
72+
return true;
73+
}
74+
75+
var childUri = new DirectoryInfo(childPath).Parent;
76+
while (childUri != null)
77+
{
78+
if (new Uri(childUri.FullName).Equals(parentUri))
79+
{
80+
return true;
81+
}
82+
83+
childUri = childUri.Parent;
84+
}
85+
86+
return false;
87+
}
88+
}
89+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Snyk.VisualStudio.Extension.Shared.Settings
2+
{
3+
using System.Collections.Generic;
4+
5+
public interface IUserStorageSettingsService
6+
{
7+
ISet<string> TrustedFolders { get; set; }
8+
}
9+
}

0 commit comments

Comments
 (0)