Skip to content

Commit bfd79bc

Browse files
committed
Introduce Audit Sources to package source VS options
1 parent 663b89f commit bfd79bc

20 files changed

Lines changed: 645 additions & 21 deletions

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

Lines changed: 162 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ 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";
23+
internal const string MonikerNuGetAudit = "nuGetAudit";
2124
internal const string MonikerMachineWideSources = "machineWidePackageSources";
2225
internal const string MonikerPackageSourceId = "packageSourceId"; // Unique identifier for the package source
2326
internal const string MonikerSourceName = "sourceName";
@@ -48,24 +51,51 @@ private List<PackageSource> LoadPackageSources(bool isMachineWide)
4851
return filteredPackageSources;
4952
}
5053

54+
private List<PackageSource> LoadAuditSources()
55+
{
56+
IEnumerable<PackageSource> auditSources = _packageSourceProvider.LoadAuditSources();
57+
return auditSources.ToList();
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 MonikerNuGetAudit:
73+
{
74+
var auditSources = await Task.Run(
75+
() => LoadAuditSources(),
76+
cancellationToken);
77+
if (auditSources.Count > 0)
78+
{
79+
return await ConvertValueOrThrow<T>(true);
80+
}
81+
return await ConvertValueOrThrow<T>(DefaultNuGetAudit);
82+
}
83+
case MonikerAuditSources:
84+
{
85+
var auditSources = await Task.Run(
86+
() => LoadAuditSources(),
87+
cancellationToken);
6188

89+
return GetValuePackageSources<T>(auditSources);
90+
}
6291
case MonikerMachineWideSources:
63-
var machineWidePackageSources = await Task.Run(
64-
() => LoadPackageSources(isMachineWide: true),
65-
cancellationToken);
66-
67-
return GetValuePackageSources<T>(machineWidePackageSources);
92+
{
93+
var machineWidePackageSources = await Task.Run(
94+
() => LoadPackageSources(isMachineWide: true),
95+
cancellationToken);
6896

97+
return GetValuePackageSources<T>(machineWidePackageSources);
98+
}
6999
default: break;
70100
}
71101

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

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

86110
try
@@ -90,7 +114,14 @@ public override async Task<ExternalSettingOperationResult> SetValueAsync<T>(stri
90114

91115
switch (moniker)
92116
{
117+
case MonikerNuGetAudit:
118+
return (ExternalSettingOperationResult)ExternalSettingOperationResult.Success.Instance;
93119
case MonikerPackageSources:
120+
var packageSourcesList = value as IReadOnlyList<IDictionary<string, object>>;
121+
if (packageSourcesList is null)
122+
{
123+
throw new InvalidOperationException();
124+
}
94125
return await Task.Run(
95126
() =>
96127
{
@@ -99,10 +130,28 @@ public override async Task<ExternalSettingOperationResult> SetValueAsync<T>(stri
99130
return savePackageSourcesResult.result;
100131
},
101132
cancellationToken);
102-
133+
case MonikerAuditSources:
134+
var auditSourceList = value as IReadOnlyList<IDictionary<string, object>>;
135+
if (auditSourceList is null)
136+
{
137+
throw new InvalidOperationException();
138+
}
139+
return await Task.Run(
140+
() =>
141+
{
142+
(ExternalSettingOperationResult result, bool hasAnyHiddenPropertyChanged) saveAuditSourcesResult = SaveAuditSources(auditSourceList, cancellationToken);
143+
hasAnyHiddenPropertyChanged = saveAuditSourcesResult.hasAnyHiddenPropertyChanged;
144+
return saveAuditSourcesResult.result;
145+
},
146+
cancellationToken);
103147
case MonikerMachineWideSources:
148+
var machineWidePackageSourcesList = value as IReadOnlyList<IDictionary<string, object>>;
149+
if (machineWidePackageSourcesList is null)
150+
{
151+
throw new InvalidOperationException();
152+
}
104153
return await Task.Run(
105-
() => SetIsEnabledOnMachineWidePackageSources(packageSourcesList, cancellationToken),
154+
() => SetIsEnabledOnMachineWidePackageSources(machineWidePackageSourcesList, cancellationToken),
106155
cancellationToken);
107156

108157
default:
@@ -237,6 +286,74 @@ private ExternalSettingOperationResult SetIsEnabledOnMachineWidePackageSources(
237286
return (result, hasAnyHiddenPropertyChanged);
238287
}
239288

289+
private (ExternalSettingOperationResult result, bool hasAnyHiddenPropertyChanged) SaveAuditSources(
290+
IReadOnlyList<IDictionary<string, object>> auditSourceDictionaryList,
291+
CancellationToken cancellationToken)
292+
{
293+
bool hasAnyHiddenPropertyChanged = false;
294+
ExternalSettingOperationResult result;
295+
296+
try
297+
{
298+
List<PackageSource> auditSources = new List<PackageSource>(capacity: auditSourceDictionaryList.Count);
299+
List<PackageSource> existingAuditSources = LoadAuditSources();
300+
bool hasAnyPackageSourceNameChanged = false;
301+
302+
foreach (Dictionary<string, object> packageSourceDictionary in auditSourceDictionaryList)
303+
{
304+
cancellationToken.ThrowIfCancellationRequested();
305+
306+
string name = packageSourceDictionary[MonikerSourceName].ToString();
307+
string lookupName;
308+
309+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have a Package ID.
310+
if (packageSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
311+
{
312+
lookupName = packageSourceIdObj.ToString();
313+
314+
if (!string.Equals(lookupName, name, StringComparison.CurrentCultureIgnoreCase))
315+
{
316+
// Changing the ID needs to refresh Unified Settings since the ID is a hidden property.
317+
hasAnyPackageSourceNameChanged = true;
318+
}
319+
}
320+
else // Newly added Package Sources will not have a Package ID yet.
321+
{
322+
lookupName = name;
323+
}
324+
325+
string source = packageSourceDictionary[MonikerSourceUrl].ToString();
326+
327+
PackageSource packageSource =
328+
PackageSourceValidator.FindExistingOrCreate(
329+
lookupName,
330+
source,
331+
name,
332+
isEnabled: true,
333+
allowInsecureConnections: false,
334+
existingAuditSources);
335+
336+
auditSources.Add(packageSource);
337+
}
338+
339+
_packageSourceProvider.SaveAuditSources(auditSources);
340+
341+
hasAnyHiddenPropertyChanged = hasAnyPackageSourceNameChanged;
342+
343+
result = ExternalSettingOperationResult.Success.Instance;
344+
}
345+
#pragma warning disable CA1031 // Do not catch general exception types
346+
catch (Exception ex) when (!(ex is OperationCanceledException && cancellationToken.IsCancellationRequested))
347+
#pragma warning restore CA1031 // Do not catch general exception types
348+
{
349+
result = CreateSettingErrorResult(ex.Message, isTransient: true);
350+
ActivityLog.LogError(ExceptionHelper.LogEntrySource, ex.ToString());
351+
}
352+
353+
return (result, hasAnyHiddenPropertyChanged);
354+
}
355+
356+
240357
private static PackageSource ParsePackageSource(IReadOnlyDictionary<string, object> packageSourceDictionary)
241358
{
242359
string name = packageSourceDictionary[MonikerSourceName].ToString().Trim();
@@ -264,6 +381,28 @@ private static PackageSource ParsePackageSource(IReadOnlyDictionary<string, obje
264381
return packageSource;
265382
}
266383

384+
private static PackageSource ParseAuditSource(IReadOnlyDictionary<string, object> auditSourceDictionary)
385+
{
386+
string name = auditSourceDictionary[MonikerSourceName].ToString().Trim();
387+
string? lookupName;
388+
389+
// Package Sources that were pre-existing in the NuGet.Config when GetValueAsync was called will have a Package ID.
390+
if (auditSourceDictionary.TryGetValue(MonikerPackageSourceId, out object packageSourceIdObj))
391+
{
392+
lookupName = packageSourceIdObj.ToString().Trim();
393+
}
394+
else // Newly added Package Sources will not have a Package ID yet.
395+
{
396+
lookupName = name;
397+
}
398+
399+
string source = auditSourceDictionary[MonikerSourceUrl].ToString().Trim();
400+
401+
var packageSource = new PackageSource(source, lookupName, isEnabled: true);
402+
403+
return packageSource;
404+
}
405+
267406
private static ExternalSettingOperationResult<T> GetValuePackageSources<T>(List<PackageSource> packageSources)
268407
{
269408
ExternalSettingOperationResult<T> result;
@@ -317,7 +456,9 @@ public OneOrMany<SettingMessage> ValidateArrayItemProperty(
317456
{
318457
var settingMessages = new OneOrMany<SettingMessage>();
319458

320-
if (arraySettingMoniker != MonikerPackageSources)
459+
bool isAuditSources = arraySettingMoniker == MonikerAuditSources;
460+
bool isPackageSources = arraySettingMoniker == MonikerPackageSources;
461+
if (!isPackageSources && !isAuditSources)
321462
{
322463
return settingMessages;
323464
}
@@ -336,7 +477,9 @@ public OneOrMany<SettingMessage> ValidateArrayItemProperty(
336477
case MonikerSourceUrl:
337478
{
338479
var packageSourceDictionary = arraySettingContent[arrayItemIndex];
339-
var result = ParsePackageSource(packageSourceDictionary);
480+
PackageSource result = isPackageSources
481+
? ParsePackageSource(packageSourceDictionary)
482+
: ParseAuditSource(packageSourceDictionary);
340483

341484
var isValidSource = PackageSourceValidator.IsValidSource(result);
342485
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_Checkbox" xml:space="preserve">
256+
<value>Use separate sources for vulnerability audit</value>
257+
</data>
258+
<data name="Text_AuditSources_HowToRemove" xml:space="preserve">
259+
<value>Remove all audit sources to revert to using package sources for vulnerability data.</value>
260+
</data>
261+
<data name="Text_AuditSources_Description" xml:space="preserve">
262+
<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>
263+
</data>
252264
</root>

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,21 @@
132132
<target state="translated">Povolit nezabezpečená připojení</target>
133133
<note />
134134
</trans-unit>
135+
<trans-unit id="Text_AuditSources_Checkbox">
136+
<source>Use separate sources for vulnerability audit</source>
137+
<target state="new">Use separate sources for vulnerability audit</target>
138+
<note />
139+
</trans-unit>
140+
<trans-unit id="Text_AuditSources_Description">
141+
<source>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)</source>
142+
<target state="new">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)</target>
143+
<note />
144+
</trans-unit>
145+
<trans-unit id="Text_AuditSources_HowToRemove">
146+
<source>Remove all audit sources to revert to using package sources for vulnerability data.</source>
147+
<target state="new">Remove all audit sources to revert to using package sources for vulnerability data.</target>
148+
<note />
149+
</trans-unit>
135150
<trans-unit id="Text_ConfigurationFiles_CommonLink">
136151
<source>Common NuGet configurations: [How settings are applied](https://aka.ms/nuget/how-settings-are-applied/)</source>
137152
<target state="translated">Běžné konfigurace NuGet: [Způsob použití nastavení](https://aka.ms/nuget/how-settings-are-applied/)</target>
@@ -177,6 +192,11 @@
177192
<target state="translated">Zdroj</target>
178193
<note />
179194
</trans-unit>
195+
<trans-unit id="Text_PackageSources_Description">
196+
<source>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)</source>
197+
<target state="new">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)</target>
198+
<note />
199+
</trans-unit>
180200
<trans-unit id="Text_PackageSources_MachineWide">
181201
<source>Machine-wide package sources</source>
182202
<target state="translated">Zdroje balíčků v rámci celého počítače</target>

0 commit comments

Comments
 (0)