Skip to content

Commit 3cbc2d6

Browse files
authored
Introduce Audit Sources to package source VS options (#6970)
1 parent da6d5ae commit 3cbc2d6

25 files changed

Lines changed: 1072 additions & 33 deletions

File tree

src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Options/PackageSourceValidator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal static PackageSource FindExistingOrCreate(
2222
string name,
2323
bool isEnabled,
2424
bool allowInsecureConnections,
25-
List<PackageSource> packageSources)
25+
IReadOnlyList<PackageSource> packageSources)
2626
{
2727
string trimmedLookupName = lookupName?.Trim() ?? string.Empty;
2828
if (string.IsNullOrEmpty(trimmedLookupName))
@@ -169,7 +169,7 @@ private static void EnsureUniqueSources(List<PackageSource> packageSources)
169169
}
170170
}
171171

172-
private static PackageSource? FindByName(string packageSourceName, List<PackageSource> packageSources)
172+
private static PackageSource? FindByName(string packageSourceName, IReadOnlyList<PackageSource> packageSources)
173173
{
174174
_ = packageSources ?? throw new ArgumentNullException(nameof(packageSources));
175175

src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Options/PackageSourcesPage.cs

Lines changed: 147 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ namespace NuGet.PackageManagement.VisualStudio.Options
1717
[Guid("15C605EC-4FD7-446B-BA4A-75ECF0C0B2D0")]
1818
public class PackageSourcesPage : NuGetExternalSettingsProvider, IExternalSettingValidator
1919
{
20+
internal const bool DefaultNuGetAudit = false;
2021
internal const string MonikerPackageSources = "packageSources";
22+
internal const string MonikerAuditSources = "auditSources";
2123
internal const string MonikerMachineWideSources = "machineWidePackageSources";
2224
internal const string MonikerPackageSourceId = "packageSourceId"; // Unique identifier for the package source
2325
internal const string MonikerSourceName = "sourceName";
@@ -40,32 +42,49 @@ internal override void VsSettings_SettingsChanged(object sender, EventArgs e)
4042
base.VsSettings_SettingsChanged(sender, e);
4143
}
4244

43-
private List<PackageSource> LoadPackageSources(bool isMachineWide)
45+
private IReadOnlyList<PackageSource> LoadPackageSources(bool isMachineWide)
4446
{
45-
IEnumerable<PackageSource> all = _packageSourceProvider.LoadPackageSources();
46-
List<PackageSource> filteredPackageSources = all
47-
.Where(packageSource => packageSource.IsMachineWide == isMachineWide).ToList();
47+
IReadOnlyList<PackageSource> filteredPackageSources = _packageSourceProvider.LoadPackageSources()
48+
.Where(packageSource => packageSource.IsMachineWide == isMachineWide)
49+
.ToList()
50+
.AsReadOnly();
4851
return filteredPackageSources;
4952
}
5053

54+
private IReadOnlyList<PackageSource> LoadAuditSources()
55+
{
56+
var auditSources = _packageSourceProvider.LoadAuditSources();
57+
return auditSources;
58+
}
59+
5160
public override async Task<ExternalSettingOperationResult<T>> GetValueAsync<T>(string moniker, CancellationToken cancellationToken)
5261
{
5362
switch (moniker)
5463
{
5564
case MonikerPackageSources:
56-
var packageSources = await Task.Run(
57-
() => LoadPackageSources(isMachineWide: false),
58-
cancellationToken);
65+
{
66+
var packageSources = await Task.Run(
67+
() => LoadPackageSources(isMachineWide: false),
68+
cancellationToken);
5969

60-
return GetValuePackageSources<T>(packageSources);
70+
return GetValuePackageSources<T>(packageSources);
71+
}
72+
case MonikerAuditSources:
73+
{
74+
var auditSources = await Task.Run(
75+
() => LoadAuditSources(),
76+
cancellationToken);
6177

78+
return GetValuePackageSources<T>(auditSources);
79+
}
6280
case MonikerMachineWideSources:
63-
var machineWidePackageSources = await Task.Run(
64-
() => LoadPackageSources(isMachineWide: true),
65-
cancellationToken);
66-
67-
return GetValuePackageSources<T>(machineWidePackageSources);
81+
{
82+
var machineWidePackageSources = await Task.Run(
83+
() => LoadPackageSources(isMachineWide: true),
84+
cancellationToken);
6885

86+
return GetValuePackageSources<T>(machineWidePackageSources);
87+
}
6988
default: break;
7089
}
7190

@@ -75,12 +94,6 @@ public override async Task<ExternalSettingOperationResult<T>> GetValueAsync<T>(s
7594

7695
public override async Task<ExternalSettingOperationResult> SetValueAsync<T>(string moniker, T value, CancellationToken cancellationToken)
7796
{
78-
var packageSourcesList = value as IReadOnlyList<IDictionary<string, object>>;
79-
if (packageSourcesList is null)
80-
{
81-
throw new InvalidOperationException();
82-
}
83-
8497
bool hasAnyHiddenPropertyChanged = false;
8598

8699
try
@@ -91,6 +104,7 @@ public override async Task<ExternalSettingOperationResult> SetValueAsync<T>(stri
91104
switch (moniker)
92105
{
93106
case MonikerPackageSources:
107+
var packageSourcesList = (IReadOnlyList<IDictionary<string, object>>)value;
94108
return await Task.Run(
95109
() =>
96110
{
@@ -99,10 +113,20 @@ public override async Task<ExternalSettingOperationResult> SetValueAsync<T>(stri
99113
return savePackageSourcesResult.result;
100114
},
101115
cancellationToken);
102-
116+
case MonikerAuditSources:
117+
var auditSourceList = (IReadOnlyList<IDictionary<string, object>>)value;
118+
return await Task.Run(
119+
() =>
120+
{
121+
(ExternalSettingOperationResult result, bool hasAnyHiddenPropertyChanged) saveAuditSourcesResult = SaveAuditSources(auditSourceList, cancellationToken);
122+
hasAnyHiddenPropertyChanged = saveAuditSourcesResult.hasAnyHiddenPropertyChanged;
123+
return saveAuditSourcesResult.result;
124+
},
125+
cancellationToken);
103126
case MonikerMachineWideSources:
127+
var machineWidePackageSourcesList = (IReadOnlyList<IDictionary<string, object>>)value;
104128
return await Task.Run(
105-
() => SetIsEnabledOnMachineWidePackageSources(packageSourcesList, cancellationToken),
129+
() => SetIsEnabledOnMachineWidePackageSources(machineWidePackageSourcesList, cancellationToken),
106130
cancellationToken);
107131

108132
default:
@@ -178,7 +202,7 @@ private ExternalSettingOperationResult SetIsEnabledOnMachineWidePackageSources(
178202
try
179203
{
180204
List<PackageSource> packageSources = new List<PackageSource>(capacity: packageSourceDictionaryList.Count);
181-
List<PackageSource> existingPackageSources = LoadPackageSources(isMachineWide: false);
205+
IReadOnlyList<PackageSource> existingPackageSources = LoadPackageSources(isMachineWide: false);
182206
bool hasAnyPackageSourceNameChanged = false;
183207

184208
foreach (Dictionary<string, object> packageSourceDictionary in packageSourceDictionaryList)
@@ -188,7 +212,7 @@ private ExternalSettingOperationResult SetIsEnabledOnMachineWidePackageSources(
188212
string name = packageSourceDictionary[MonikerSourceName].ToString();
189213
string lookupName;
190214

191-
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have a Package ID.
215+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have an ID.
192216
if (packageSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
193217
{
194218
lookupName = packageSourceIdObj.ToString();
@@ -199,7 +223,7 @@ private ExternalSettingOperationResult SetIsEnabledOnMachineWidePackageSources(
199223
hasAnyPackageSourceNameChanged = true;
200224
}
201225
}
202-
else // Newly added Package Sources will not have a Package ID yet.
226+
else // Newly added Package Sources will not have an ID yet.
203227
{
204228
lookupName = name;
205229
}
@@ -237,17 +261,85 @@ private ExternalSettingOperationResult SetIsEnabledOnMachineWidePackageSources(
237261
return (result, hasAnyHiddenPropertyChanged);
238262
}
239263

264+
private (ExternalSettingOperationResult result, bool hasAnyHiddenPropertyChanged) SaveAuditSources(
265+
IReadOnlyList<IDictionary<string, object>> auditSourceDictionaryList,
266+
CancellationToken cancellationToken)
267+
{
268+
bool hasAnyHiddenPropertyChanged = false;
269+
ExternalSettingOperationResult result;
270+
271+
try
272+
{
273+
List<PackageSource> auditSources = new List<PackageSource>(capacity: auditSourceDictionaryList.Count);
274+
IReadOnlyList<PackageSource> existingAuditSources = LoadAuditSources();
275+
bool hasAnyPackageSourceNameChanged = false;
276+
277+
foreach (Dictionary<string, object> packageSourceDictionary in auditSourceDictionaryList)
278+
{
279+
cancellationToken.ThrowIfCancellationRequested();
280+
281+
string name = packageSourceDictionary[MonikerSourceName].ToString();
282+
string lookupName;
283+
284+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have an ID.
285+
if (packageSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
286+
{
287+
lookupName = packageSourceIdObj.ToString();
288+
289+
if (!string.Equals(lookupName, name, StringComparison.CurrentCultureIgnoreCase))
290+
{
291+
// Changing the ID needs to refresh Unified Settings since the ID is a hidden property.
292+
hasAnyPackageSourceNameChanged = true;
293+
}
294+
}
295+
else // Newly added Package Sources will not have an ID yet.
296+
{
297+
lookupName = name;
298+
}
299+
300+
string source = packageSourceDictionary[MonikerSourceUrl].ToString();
301+
302+
PackageSource packageSource =
303+
PackageSourceValidator.FindExistingOrCreate(
304+
lookupName,
305+
source,
306+
name,
307+
isEnabled: true,
308+
allowInsecureConnections: false,
309+
existingAuditSources);
310+
311+
auditSources.Add(packageSource);
312+
}
313+
314+
_packageSourceProvider.SaveAuditSources(auditSources);
315+
316+
hasAnyHiddenPropertyChanged = hasAnyPackageSourceNameChanged;
317+
318+
result = ExternalSettingOperationResult.Success.Instance;
319+
}
320+
#pragma warning disable CA1031 // Do not catch general exception types
321+
catch (Exception ex) when (!(ex is OperationCanceledException && cancellationToken.IsCancellationRequested))
322+
#pragma warning restore CA1031 // Do not catch general exception types
323+
{
324+
result = CreateSettingErrorResult(ex.Message, isTransient: true);
325+
ActivityLog.LogError(ExceptionHelper.LogEntrySource, ex.ToString());
326+
}
327+
328+
return (result, hasAnyHiddenPropertyChanged);
329+
}
330+
331+
240332
private static PackageSource ParsePackageSource(IReadOnlyDictionary<string, object> packageSourceDictionary)
241333
{
242334
string name = packageSourceDictionary[MonikerSourceName].ToString().Trim();
243335
string? lookupName;
244336

245-
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have a Package ID.
337+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have an ID.
246338
if (packageSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
247339
{
248340
lookupName = packageSourceIdObj.ToString().Trim();
249341
}
250-
else // Newly added Package Sources will not have a Package ID yet.
342+
else // Newly added Package Sources will not have an ID yet.
251343
{
252344
lookupName = name;
253345
}
@@ -264,7 +356,29 @@ private static PackageSource ParsePackageSource(IReadOnlyDictionary<string, obje
264356
return packageSource;
265357
}
266358

267-
private static ExternalSettingOperationResult<T> GetValuePackageSources<T>(List<PackageSource> packageSources)
359+
private static PackageSource ParseAuditSource(IReadOnlyDictionary<string, object> auditSourceDictionary)
360+
{
361+
string name = auditSourceDictionary[MonikerSourceName].ToString().Trim();
362+
string? lookupName;
363+
364+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have an ID.
365+
if (auditSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
366+
{
367+
lookupName = packageSourceIdObj.ToString().Trim();
368+
}
369+
else // Newly added Package Sources will not have an ID yet.
370+
{
371+
lookupName = name;
372+
}
373+
374+
string source = auditSourceDictionary[MonikerSourceUrl].ToString().Trim();
375+
376+
var packageSource = new PackageSource(source, lookupName, isEnabled: true);
377+
378+
return packageSource;
379+
}
380+
381+
private static ExternalSettingOperationResult<T> GetValuePackageSources<T>(IReadOnlyList<PackageSource> packageSources)
268382
{
269383
ExternalSettingOperationResult<T> result;
270384

@@ -317,7 +431,9 @@ public OneOrMany<SettingMessage> ValidateArrayItemProperty(
317431
{
318432
var settingMessages = new OneOrMany<SettingMessage>();
319433

320-
if (arraySettingMoniker != MonikerPackageSources)
434+
bool isAuditSources = arraySettingMoniker == MonikerAuditSources;
435+
bool isPackageSources = arraySettingMoniker == MonikerPackageSources;
436+
if (!isPackageSources && !isAuditSources)
321437
{
322438
return settingMessages;
323439
}
@@ -336,7 +452,9 @@ public OneOrMany<SettingMessage> ValidateArrayItemProperty(
336452
case MonikerSourceUrl:
337453
{
338454
var packageSourceDictionary = arraySettingContent[arrayItemIndex];
339-
var result = ParsePackageSource(packageSourceDictionary);
455+
PackageSource result = isPackageSources
456+
? ParsePackageSource(packageSourceDictionary)
457+
: ParseAuditSource(packageSourceDictionary);
340458

341459
var isValidSource = PackageSourceValidator.IsValidSource(result);
342460
if (!isValidSource)

src/NuGet.Clients/NuGet.Tools/Resources.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NuGet.Clients/NuGet.Tools/Resources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,16 @@
249249
<data name="Text_AllowInsecureConnections_Header" xml:space="preserve">
250250
<value>Allow Insecure Connections</value>
251251
</data>
252+
<data name="Text_PackageSources_Description" xml:space="preserve">
253+
<value>Package sources define where NuGet retrieves packages for install, restore, audit, and update operations. [Learn more about package sources](https://learn.microsoft.com/nuget/reference/nuget-config-file#packagesources)</value>
254+
</data>
255+
<data name="Text_AuditSources_HowToRemove" xml:space="preserve">
256+
<value>Remove all audit sources to revert to using package sources for vulnerability data.</value>
257+
</data>
258+
<data name="Text_AuditSources_Description" xml:space="preserve">
259+
<value>Audit sources provide vulnerability data during restore without acting as package sources. If no audit sources are configured, NuGet Audit uses package sources and suppresses warning NU1905. [Learn more about audit sources](https://learn.microsoft.com/nuget/reference/nuget-config-file#auditsources)</value>
260+
</data>
261+
<data name="Text_AuditSources_Title" xml:space="preserve">
262+
<value>Audit sources</value>
263+
</data>
252264
</root>

0 commit comments

Comments
 (0)