Skip to content

Commit 1fdeb2d

Browse files
author
Asaf Agami
authored
Chore: add user agent headers [head-373] (#237)
1 parent 1b3a4ac commit 1fdeb2d

12 files changed

Lines changed: 139 additions & 65 deletions

File tree

.editorconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
# SA1600: Elements should be documented
44
dotnet_diagnostic.SA1600.severity = none
5+
6+
dotnet_diagnostic.VSTHRD200.severity = suggestion

Snyk.Code.Library/Api/SnykCodeClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public class SnykCodeClient : ISnykCodeClient
4141
/// <param name="token">User token.</param>
4242
/// <param name="flowName">Context flow name.</param>
4343
/// <param name="orgName">User organization name.</param>
44-
public SnykCodeClient(string baseUrl, AuthenticationToken token, string flowName, string orgName)
44+
public SnykCodeClient(string baseUrl, AuthenticationToken token, string flowName, string orgName, HttpClient httpClient = null)
4545
{
46-
this.httpClient = HttpClientFactory.NewHttpClient(token, baseUrl);
46+
this.httpClient = httpClient ?? HttpClientFactory.NewHttpClient(token, baseUrl);
4747

4848
Logger.Information("Create http client with with url {BaseUrl}.", baseUrl);
4949

Snyk.Code.Library/Service/CodeServiceFactory.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Snyk.Code.Library.Api;
44
using Snyk.Common;
55
using Snyk.Common.Authentication;
6+
using System.Net.Http;
67

78
/// <summary>
89
/// Factory to create SnykCode services. This class hide intance createtion.
@@ -23,9 +24,10 @@ public static ISnykCodeService CreateSnykCodeService(
2324
string endpoint,
2425
IFileProvider fileProvider,
2526
string flowName,
26-
string orgName)
27+
string orgName,
28+
HttpClient httpClient = null)
2729
{
28-
var codeClient = new SnykCodeClient(endpoint, apiToken, flowName, orgName);
30+
var codeClient = new SnykCodeClient(endpoint, apiToken, flowName, orgName, httpClient);
2931

3032
var filterService = new FiltersService(codeClient);
3133

Snyk.Common/HttpClientFactory.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
using System.Net;
55
using System.Net.Http;
66
using System.Net.Http.Headers;
7+
using System.Runtime.InteropServices;
8+
using global::Sentry;
79
using Snyk.Common.Authentication;
810

911
/// <summary>
1012
/// Factory for <see cref="HttpClient"/>.
1113
/// </summary>
12-
public class HttpClientFactory
14+
public static class HttpClientFactory
1315
{
1416
/// <summary>
1517
/// Create new <see cref="HttpClient"/> by base URL and API token.
@@ -49,4 +51,20 @@ public static HttpClient NewHttpClient(AuthenticationToken token, string baseUrl
4951

5052
return httpClient;
5153
}
54+
55+
public static HttpClient WithUserAgent(this HttpClient httpClient, string ideVersion, string pluginVersionString)
56+
{
57+
if (string.IsNullOrEmpty(ideVersion) || string.IsNullOrEmpty(pluginVersionString))
58+
{
59+
return httpClient;
60+
}
61+
62+
var os = RuntimeInformation.OSDescription;
63+
var arch = RuntimeInformation.ProcessArchitecture.ToString();
64+
65+
var header = $"VISUAL_STUDIO/{ideVersion} ({os};{arch}) VISUAL_STUDIO/{pluginVersionString} (VISUAL_STUDIO/{ideVersion})";
66+
67+
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(header);
68+
return httpClient;
69+
}
5270
}

Snyk.Common/Service/SnykApiService.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ public class SnykApiService : ISnykApiService
1313
private const string SastSettingsApiName = "v1/cli-config/settings/sast";
1414

1515
private readonly ISnykOptions options;
16+
private readonly string vsVersion;
1617

1718
/// <summary>
1819
/// Initializes a new instance of the <see cref="SnykApiService"/> class.
1920
/// </summary>
2021
/// <param name="options">Options instance.</param>
21-
public SnykApiService(ISnykOptions options)
22+
/// <param name="vsVersion">The IDE major version (17 for vs22)</param>
23+
/// <param name="pluginVersion">The full plugin version</param>
24+
public SnykApiService(ISnykOptions options, string vsVersion = "", string pluginVersion = "")
2225
{
2326
this.options = options;
27+
this.vsVersion = vsVersion ?? "";
2428
}
2529

26-
private HttpClient HttpClient => HttpClientFactory.NewHttpClient(this.options.ApiToken);
30+
private HttpClient HttpClient => HttpClientFactory.NewHttpClient(this.options.ApiToken)
31+
.WithUserAgent(this.vsVersion, SnykExtension.Version);
2732

2833
/// <inheritdoc/>
2934
public async Task<SnykUser> GetUserAsync()

Snyk.Common/SnykExtension.cs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,14 @@ public class SnykExtension
1818
private const string AppSettingsDevelopmentFileName = "appsettings.development.json";
1919
private const string AppSettingsFileName = "appsettings.json";
2020

21-
private static string version = string.Empty;
22-
2321
private static string extensionDirectoryPath;
24-
2522
private static SnykAppSettings appSettings;
23+
private static readonly Lazy<string> versionLazy = new(GetIntegrationVersion);
2624

2725
/// <summary>
2826
/// Gets extension version.
2927
/// </summary>
30-
public static string Version => GetIntegrationVersion();
28+
public static string Version => versionLazy.Value;
3129

3230
/// <summary>
3331
/// Gets <see cref="SnykAppSettings"/> from the appsettings.json file.
@@ -81,36 +79,33 @@ public static string GetExtensionDirectoryPath()
8179
/// <returns>String.</returns>
8280
private static string GetIntegrationVersion()
8381
{
84-
if (string.IsNullOrEmpty(version))
82+
try
8583
{
86-
try
87-
{
88-
string extensionPath = GetExtensionDirectoryPath();
84+
var extensionPath = GetExtensionDirectoryPath();
8985

90-
string manifestPath = Path.Combine(extensionPath, "extension.vsixmanifest");
91-
92-
var xmlDocument = new XmlDocument();
93-
xmlDocument.Load(manifestPath);
94-
95-
if (xmlDocument.DocumentElement == null || xmlDocument.DocumentElement.Name != "PackageManifest")
96-
{
97-
return "UNKNOWN";
98-
}
86+
var manifestPath = Path.Combine(extensionPath, "extension.vsixmanifest");
9987

100-
var metaData = xmlDocument.DocumentElement.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Metadata");
101-
var identity = metaData.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Identity");
88+
var xmlDocument = new XmlDocument();
89+
xmlDocument.Load(manifestPath);
10290

103-
version = identity.GetAttribute("Version");
104-
}
105-
catch (Exception e)
91+
if (xmlDocument.DocumentElement?.Name != "PackageManifest")
10692
{
107-
// The Exception.ToString() containing the exception type, message,
108-
// stack trace, and all of these things again for nested/inner exceptions.
109-
Console.Error.WriteLine(e.ToString());
93+
return "UNKNOWN";
11094
}
111-
}
11295

113-
return version;
96+
var metaData = xmlDocument.DocumentElement.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Metadata");
97+
var identity = metaData.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Identity");
98+
99+
return identity.GetAttribute("Version");
100+
}
101+
catch (Exception e)
102+
{
103+
// The Exception.ToString() containing the exception type, message,
104+
// stack trace, and all of these things again for nested/inner exceptions.
105+
Console.Error.WriteLine(e.ToString());
106+
107+
return string.Empty;
108+
}
114109
}
115110
}
116111
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<Reference Include="System.Windows.Forms" />
8484
<Reference Include="System.Xaml" />
8585
<Reference Include="System.Xml" />
86+
<Reference Include="System.Xml.Linq" />
8687
<Reference Include="WindowsBase" />
8788
</ItemGroup>
8889
<ItemGroup>

Snyk.VisualStudio.Extension.Shared/CLI/SnykCli.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public class SnykCli : ICli
3434
/// <summary>
3535
/// Initializes a new instance of the <see cref="SnykCli"/> class.
3636
/// </summary>
37-
public SnykCli(ISnykOptions options)
37+
public SnykCli(ISnykOptions options, string ideVersion = "")
3838
{
39-
this.ConsoleRunner = new SnykConsoleRunner();
39+
this.ConsoleRunner = new SnykConsoleRunner(ideVersion);
4040
this.options = options;
4141
}
4242

Snyk.VisualStudio.Extension.Shared/CLI/SnykConsoleRunner.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ public class SnykConsoleRunner
1616
private static readonly ILogger Logger = LogManager.ForContext<SnykConsoleRunner>();
1717

1818
private bool isStopped = false;
19+
private readonly string ideVersion;
20+
21+
public SnykConsoleRunner(string ideVersion = "")
22+
{
23+
this.ideVersion = ideVersion;
24+
}
1925

2026
/// <summary>
2127
/// Gets or sets a value indicating whether process.
@@ -70,6 +76,8 @@ public virtual Process CreateProcess(string fileName, string arguments, StringDi
7076

7177
processStartInfo.EnvironmentVariables["SNYK_INTEGRATION_NAME"] = SnykExtension.IntegrationName;
7278
processStartInfo.EnvironmentVariables["SNYK_INTEGRATION_VERSION"] = SnykExtension.Version;
79+
processStartInfo.EnvironmentVariables["SNYK_INTEGRATION_ENVIRONMENT_NAME"] = SnykExtension.IntegrationName;
80+
processStartInfo.EnvironmentVariables["SNYK_INTEGRATION_ENVIRONMENT_VERSION"] = this.ideVersion;
7381

7482
processStartInfo.WorkingDirectory = workingDirectory;
7583

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
44
{
55
using System;
66
using System.IO;
7+
using System.Linq;
8+
using System.Reflection;
79
using System.Threading;
810
using System.Threading.Tasks;
11+
using System.Xml;
12+
using System.Xml.Linq;
913
using EnvDTE;
1014
using EnvDTE80;
1115
using Microsoft.VisualStudio.Settings;
16+
using Microsoft.VisualStudio.Shell.Interop;
1217
using Microsoft.VisualStudio.Shell.Settings;
18+
using Newtonsoft.Json.Linq;
1319
using Serilog;
1420
using Snyk.Analytics;
1521
using Snyk.Code.Library.Service;
@@ -34,7 +40,7 @@ public class SnykService : ISnykServiceProvider, ISnykService
3440
private static readonly ILogger Logger = LogManager.ForContext<SnykService>();
3541

3642
private readonly IAsyncServiceProvider serviceProvider;
37-
43+
private readonly string vsVersion;
3844
private SettingsManager settingsManager;
3945

4046
private SnykVsThemeService vsThemeService;
@@ -61,7 +67,12 @@ public class SnykService : ISnykServiceProvider, ISnykService
6167
/// Initializes a new instance of the <see cref="SnykService"/> class.
6268
/// </summary>
6369
/// <param name="serviceProvider">Snyk service provider implementation.</param>
64-
public SnykService(IAsyncServiceProvider serviceProvider) => this.serviceProvider = serviceProvider;
70+
/// <param name="vsVersion">The version of the IDE</param>
71+
public SnykService(IAsyncServiceProvider serviceProvider, string vsVersion = "")
72+
{
73+
this.serviceProvider = serviceProvider;
74+
this.vsVersion = vsVersion;
75+
}
6576

6677
/// <summary>
6778
/// Gets Snyk options implementation.
@@ -172,7 +183,7 @@ public ISnykApiService ApiService
172183
{
173184
if (this.apiService == null)
174185
{
175-
this.apiService = new SnykApiService(this.Options);
186+
this.apiService = new SnykApiService(this.Options, this.vsVersion, SnykExtension.Version);
176187

177188
this.Options.SettingsChanged += this.OnSettingsChanged;
178189
}
@@ -221,13 +232,6 @@ public ISentryService SentryService
221232
/// <returns>Result VS service instance</returns>
222233
public async Task<object> GetServiceAsync(Type serviceType) => await this.serviceProvider.GetServiceAsync(serviceType);
223234

224-
/// <summary>
225-
/// Get Visual Studio service by type (not async method).
226-
/// </summary>
227-
/// <param name="serviceType">Needed service type.</param>
228-
/// <returns>Result VS service instance</returns>
229-
public object GetService(Type serviceType) => null;
230-
231235
/// <summary>
232236
/// Initialize service.
233237
/// </summary>
@@ -238,7 +242,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken)
238242
try
239243
{
240244
Logger.Information("Initialize Snyk services");
241-
245+
Logger.Information("Plugin version is {Version}", SnykExtension.Version);
242246
await this.Package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
243247

244248
this.settingsManager = new ShellSettingsManager(this.Package);
@@ -269,7 +273,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken)
269273
/// Create new instance of SnykCli class with Options and Logger parameters.
270274
/// </summary>
271275
/// <returns>New SnykCli instance.</returns>
272-
public ICli NewCli() => new SnykCli(this.Options);
276+
public ICli NewCli() => new SnykCli(this.Options, this.vsVersion);
273277

274278
private void OnSettingsChanged(object sender, SnykSettingsChangedEventArgs e) => this.SetupSnykCodeService();
275279

@@ -278,15 +282,17 @@ private void SetupSnykCodeService()
278282
try
279283
{
280284
var options = this.Options;
281-
282285
string endpoint = new ApiEndpointResolver(this.Options).GetSnykCodeApiUrl();
286+
var httpClient = HttpClientFactory.NewHttpClient(options.ApiToken, endpoint)
287+
.WithUserAgent(this.vsVersion, SnykExtension.Version);
283288

284289
this.snykCodeService = CodeServiceFactory.CreateSnykCodeService(
285290
options.ApiToken,
286291
endpoint,
287292
this.SolutionService.FileProvider,
288293
SnykExtension.IntegrationName,
289-
options.Organization ?? string.Empty);
294+
options.Organization ?? string.Empty,
295+
httpClient);
290296

291297
VsStatusBarNotificationService.Instance.InitializeEventListeners(this.snykCodeService, options);
292298
}

0 commit comments

Comments
 (0)