Skip to content

Commit 2fac053

Browse files
authored
[NuGet Symbol Server] Add symbols delete support to gallery (#6513)
1 parent 260a5b8 commit 2fac053

20 files changed

Lines changed: 847 additions & 14 deletions

src/NuGetGallery.Core/Auditing/PackageCreatedVia.cs renamed to src/NuGetGallery.Core/Auditing/PackageAuditReasons.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,17 @@ public static class PackageCreatedVia
1515
/// </summary>
1616
public const string Web = "Created via web.";
1717
}
18+
19+
public static class PackageDeletedVia
20+
{
21+
/// <summary>
22+
/// Package has been deleted via NuGet API (nuget.exe delete)
23+
/// </summary>
24+
public const string Api = "Deleted via API.";
25+
26+
/// <summary>
27+
/// Package has been deleted via NuGet web interface (browser)
28+
/// </summary>
29+
public const string Web = "Deleted via web.";
30+
}
1831
}

src/NuGetGallery.Core/NuGetGallery.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
<Compile Include="Auditing\CredentialAuditRecord.cs" />
9292
<Compile Include="Auditing\AuditedPackageAction.cs" />
9393
<Compile Include="Auditing\IAuditingService.cs" />
94-
<Compile Include="Auditing\PackageCreatedVia.cs" />
94+
<Compile Include="Auditing\PackageAuditReasons.cs" />
9595
<Compile Include="Auditing\AuditedPackageRegistrationAction.cs" />
9696
<Compile Include="Auditing\AuditedEntities\AuditedPackageRegistration.cs" />
9797
<Compile Include="Auditing\PackageRegistrationAuditRecord.cs" />

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using NuGetGallery.AsyncFileUpload;
2222
using NuGetGallery.Auditing;
2323
using NuGetGallery.Configuration;
24+
using NuGetGallery.Diagnostics;
2425
using NuGetGallery.Filters;
2526
using NuGetGallery.Helpers;
2627
using NuGetGallery.Infrastructure.Lucene;
@@ -88,6 +89,7 @@ public partial class PackagesController
8889
private readonly IPackageOwnershipManagementService _packageOwnershipManagementService;
8990
private readonly IContentObjectService _contentObjectService;
9091
private readonly ISymbolPackageUploadService _symbolPackageUploadService;
92+
private readonly IDiagnosticsSource _trace;
9193

9294
public PackagesController(
9395
IPackageService packageService,
@@ -111,7 +113,8 @@ public PackagesController(
111113
IValidationService validationService,
112114
IPackageOwnershipManagementService packageOwnershipManagementService,
113115
IContentObjectService contentObjectService,
114-
ISymbolPackageUploadService symbolPackageUploadService)
116+
ISymbolPackageUploadService symbolPackageUploadService,
117+
IDiagnosticsService diagnosticsService)
115118
{
116119
_packageService = packageService;
117120
_uploadFileService = uploadFileService;
@@ -135,6 +138,7 @@ public PackagesController(
135138
_packageOwnershipManagementService = packageOwnershipManagementService;
136139
_contentObjectService = contentObjectService;
137140
_symbolPackageUploadService = symbolPackageUploadService;
141+
_trace = diagnosticsService?.SafeGetSource(nameof(PackagesController)) ?? throw new ArgumentNullException(nameof(diagnosticsService));
138142
}
139143

140144
[HttpGet]
@@ -220,7 +224,7 @@ private async Task<ActionResult> UploadSymbolsPackageInternal(SubmitPackageReque
220224
var existingPackageRegistration = packageForUploadingSymbols.PackageRegistration;
221225

222226
IEnumerable<User> accountsAllowedOnBehalfOf = Enumerable.Empty<User>();
223-
bool isAllowed = ActionsRequiringPermissions.UploadNewPackageVersion.CheckPermissionsOnBehalfOfAnyAccount(currentUser, existingPackageRegistration, out accountsAllowedOnBehalfOf) == PermissionsCheckResult.Allowed;
227+
bool isAllowed = ActionsRequiringPermissions.UploadSymbolPackage.CheckPermissionsOnBehalfOfAnyAccount(currentUser, existingPackageRegistration, out accountsAllowedOnBehalfOf) == PermissionsCheckResult.Allowed;
224228
if (!isAllowed)
225229
{
226230
accountsAllowedOnBehalfOf = new[] { currentUser };
@@ -363,7 +367,7 @@ private async Task<JsonResult> UploadSymbolsPackageInternal(PackageArchiveReader
363367

364368
// Evaluate the permissions for user on behalf of any account possible, since the user
365369
// could change the ownership before submitting the package.
366-
if (ActionsRequiringPermissions.UploadNewPackageVersion.CheckPermissionsOnBehalfOfAnyAccount(
370+
if (ActionsRequiringPermissions.UploadSymbolPackage.CheckPermissionsOnBehalfOfAnyAccount(
367371
currentUser, existingPackageRegistration, out accountsAllowedOnBehalfOf) != PermissionsCheckResult.Allowed)
368372
{
369373
return Json(HttpStatusCode.Conflict, new[] { string.Format(CultureInfo.CurrentCulture, Strings.PackageIdNotAvailable, existingPackageRegistration.Id) });
@@ -1161,6 +1165,40 @@ public virtual ActionResult Delete(string id, string version)
11611165
return View(model);
11621166
}
11631167

1168+
[HttpGet]
1169+
[UIAuthorize]
1170+
[RequiresAccountConfirmation("delete a symbols package")]
1171+
public virtual ActionResult DeleteSymbols(string id, string version)
1172+
{
1173+
var package = _packageService.FindPackageByIdAndVersion(id, version);
1174+
if (package == null)
1175+
{
1176+
return HttpNotFound();
1177+
}
1178+
1179+
var currentUser = GetCurrentUser();
1180+
if (ActionsRequiringPermissions.DeleteSymbolPackage.CheckPermissionsOnBehalfOfAnyAccount(currentUser, package) != PermissionsCheckResult.Allowed)
1181+
{
1182+
return HttpForbidden();
1183+
}
1184+
1185+
var model = new DeletePackageViewModel(package, currentUser, DeleteReasons);
1186+
1187+
model.VersionSelectList = new SelectList(
1188+
model
1189+
.PackageVersions
1190+
.Where(p => !p.Deleted
1191+
&& p.LatestSymbolsPackage != null
1192+
&& p.LatestSymbolsPackage.StatusKey == PackageStatus.Available)
1193+
.Select(p => new
1194+
{
1195+
text = p.NuGetVersion.ToFullString() + (p.LatestVersionSemVer2 ? " (Latest)" : string.Empty),
1196+
url = Url.DeleteSymbolsPackage(p)
1197+
}), "url", "text", Url.DeleteSymbolsPackage(model));
1198+
1199+
return View(model);
1200+
}
1201+
11641202
[UIAuthorize(Roles = "Admins")]
11651203
[RequiresAccountConfirmation("reflow a package")]
11661204
public virtual async Task<ActionResult> Reflow(string id, string version)
@@ -1278,6 +1316,67 @@ await _packageDeleteService.HardDeletePackagesAsync(
12781316
return Delete(firstPackage.PackageRegistration.Id, firstPackage.Version);
12791317
}
12801318

1319+
[UIAuthorize]
1320+
[HttpPost]
1321+
[RequiresAccountConfirmation("delete a symbols package")]
1322+
[ValidateAntiForgeryToken]
1323+
public virtual async Task<ActionResult> DeleteSymbolsPackage(string id, string version)
1324+
{
1325+
var package = _packageService.FindPackageByIdAndVersionStrict(id, version);
1326+
if (package == null)
1327+
{
1328+
return new HttpStatusCodeResult(HttpStatusCode.NotFound,
1329+
string.Format(Strings.PackageWithIdAndVersionNotFound, id, version));
1330+
}
1331+
1332+
if (ActionsRequiringPermissions.DeleteSymbolPackage.CheckPermissionsOnBehalfOfAnyAccount(GetCurrentUser(), package)
1333+
!= PermissionsCheckResult.Allowed)
1334+
{
1335+
return new HttpStatusCodeResult(HttpStatusCode.Forbidden, Strings.SymbolsPackage_UploadNotAllowed);
1336+
}
1337+
1338+
if (package.PackageRegistration.IsLocked)
1339+
{
1340+
return new HttpStatusCodeResult(HttpStatusCode.Forbidden,
1341+
string.Format(CultureInfo.CurrentCulture, Strings.PackageIsLocked, package.PackageRegistration.Id));
1342+
}
1343+
1344+
// Get all available symbol packages for a given package, ideally this should
1345+
// always return one symbol package. For thoroughness we can cleanup the data
1346+
// for any inconsistencies.
1347+
var availableSymbolPackages = package
1348+
.SymbolPackages
1349+
.Where(sp => sp.StatusKey == PackageStatus.Available);
1350+
1351+
if (availableSymbolPackages.Count() > 1)
1352+
{
1353+
_trace.Warning($"Multiple({availableSymbolPackages.Count()}) available symbol packages found for {package.Id}, {package.Version}");
1354+
}
1355+
1356+
if (availableSymbolPackages.Any())
1357+
{
1358+
foreach (var symbolPackage in availableSymbolPackages)
1359+
{
1360+
await _symbolPackageUploadService.DeleteSymbolsPackageAsync(symbolPackage);
1361+
}
1362+
1363+
TempData["Message"] = Strings.SymbolsPackage_Deleted;
1364+
1365+
await _auditingService.SaveAuditRecordAsync(
1366+
new PackageAuditRecord(package, AuditedPackageAction.SymbolsDelete, PackageDeletedVia.Web));
1367+
1368+
_telemetryService.TrackSymbolPackageDeleteEvent(package.Id, package.Version);
1369+
1370+
// Redirect to the package details page
1371+
return Redirect(Url.Package(package, relativeUrl: true));
1372+
}
1373+
else
1374+
{
1375+
return new HttpStatusCodeResult(HttpStatusCode.BadRequest,
1376+
string.Format(Strings.SymbolsPackage_PackageNotAvailable, id, version));
1377+
}
1378+
}
1379+
12811380
[UIAuthorize]
12821381
[HttpPost]
12831382
[RequiresAccountConfirmation("unlist a package")]
@@ -1697,7 +1796,7 @@ public virtual async Task<JsonResult> VerifySymbolsPackageInternal(
16971796

16981797
// Evaluate the permissions for the owner, the permissions for uploading a symbols should be same as that of
16991798
// uploading a new version of a given package.
1700-
var checkPermissionsOfUploadNewVersion = ActionsRequiringPermissions.UploadNewPackageVersion.CheckPermissions(currentUser, owner, existingPackageRegistration);
1799+
var checkPermissionsOfUploadNewVersion = ActionsRequiringPermissions.UploadSymbolPackage.CheckPermissions(currentUser, owner, existingPackageRegistration);
17011800
if (checkPermissionsOfUploadNewVersion != PermissionsCheckResult.Allowed)
17021801
{
17031802
if (checkPermissionsOfUploadNewVersion == PermissionsCheckResult.AccountFailure)

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,7 @@
17781778
<Content Include="Views\Shared\ConfirmationRequired.cshtml" />
17791779
<Content Include="Views\Pages\Contact.cshtml" />
17801780
<Content Include="Views\Shared\_DeletePackage.cshtml" />
1781+
<Content Include="Views\Packages\DeleteSymbols.cshtml" />
17811782
</ItemGroup>
17821783
<ItemGroup>
17831784
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />

src/NuGetGallery/Scripts/gallery/page-delete-package.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@
1414
e.preventDefault();
1515
}
1616
});
17+
18+
$('#delete-symbols-form').submit(function (e) {
19+
if (!confirm('Deleting this symbols package will make it unavailable for download and remove all corresponding symbols from the symbol server. Are you sure you want to continue with the delete?')) {
20+
e.preventDefault();
21+
}
22+
});
1723
});

src/NuGetGallery/Services/ActionsRequiringPermissions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,21 @@ public static class ActionsRequiringPermissions
4545
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
4646

4747
/// <summary>
48-
/// The action of uploading a symbol package for an existing package.
48+
/// The action of uploading a symbols package for an existing package.
4949
/// </summary>
5050
public static ActionRequiringPackagePermissions UploadSymbolPackage =
5151
new ActionRequiringPackagePermissions(
5252
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
5353
packageRegistrationPermissionsRequirement: PermissionsRequirement.Owner);
5454

55+
/// <summary>
56+
/// The action of deleting a symbols package for an existing package.
57+
/// </summary>
58+
public static ActionRequiringPackagePermissions DeleteSymbolPackage =
59+
new ActionRequiringPackagePermissions(
60+
accountOnBehalfOfPermissionsRequirement: RequireOwnerOrOrganizationMember,
61+
packageRegistrationPermissionsRequirement: RequireOwnerOrSiteAdmin);
62+
5563
/// <summary>
5664
/// The action of verify a package verification key.
5765
/// </summary>

src/NuGetGallery/Services/ISymbolPackageUploadService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,11 @@ public interface ISymbolPackageUploadService
3434
/// <param name="currentUser">The user performing the uploads.</param>
3535
/// <returns>Awaitable task with <see cref="SymbolPackageValidationResult"/></returns>
3636
Task<SymbolPackageValidationResult> ValidateUploadedSymbolsPackage(Stream uploadStream, User currentUser);
37+
38+
/// <summary>
39+
/// Mark the specifed symbol package for deletion and delete the corressponding snupkg as well.
40+
/// </summary>
41+
/// <param name="symbolPackage">The <see cref="SymbolPackage"/> entity to be marked for deletion</param>
42+
Task DeleteSymbolsPackageAsync(SymbolPackage symbolPackage);
3743
}
3844
}

src/NuGetGallery/Services/ITelemetryService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ public interface ITelemetryService
165165
/// <param name="packageVersion">The version of the package that has the symbols uploaded.</param>
166166
void TrackSymbolPackagePushEvent(string packageId, string packageVersion);
167167

168+
/// <summary>
169+
/// A telemetry event emitted when a symbol package is deleted.
170+
/// </summary>
171+
/// <param name="packageId">The id of the package for which symbols are deleted.</param>
172+
/// <param name="packageVersion">The version of the package for which the symbols are delted.</param>
173+
void TrackSymbolPackageDeleteEvent(string packageId, string packageVersion);
174+
168175
/// <summary>
169176
/// A telemetry event emitted when a symbol package fails to be pushed.
170177
/// </summary>

src/NuGetGallery/Services/SymbolPackageUploadService.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,5 +230,20 @@ await _symbolPackageFileService.DeletePackageFileAsync(
230230

231231
return PackageCommitResult.Success;
232232
}
233+
234+
public async Task DeleteSymbolsPackageAsync(SymbolPackage symbolPackage)
235+
{
236+
if (symbolPackage == null)
237+
{
238+
throw new ArgumentNullException(nameof(symbolPackage));
239+
}
240+
241+
if (await _symbolPackageFileService.DoesPackageFileExistAsync(symbolPackage.Package))
242+
{
243+
await _symbolPackageFileService.DeletePackageFileAsync(symbolPackage.Id, symbolPackage.Version);
244+
}
245+
246+
await _symbolPackageService.UpdateStatusAsync(symbolPackage, PackageStatus.Deleted, commitChanges: true);
247+
}
233248
}
234249
}

src/NuGetGallery/Services/TelemetryService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ internal class Events
5050
public const string AccountDeleteCompleted = "AccountDeleteCompleted";
5151
public const string AccountDeleteRequested = "AccountDeleteRequested";
5252
public const string SymbolPackagePush = "SymbolPackagePush";
53+
public const string SymbolPackageDelete = "SymbolPackageDelete";
5354
public const string SymbolPackagePushFailure = "SymbolPackagePushFailure";
5455
public const string SymbolPackageGalleryValidation = "SymbolPackageGalleryValidation";
5556
public const string PackageMetadataComplianceError = "PackageMetadataComplianceError";
@@ -409,6 +410,10 @@ public void TrackSymbolPackagePushEvent(string packageId, string packageVersion)
409410
{
410411
TrackMetricForSymbolPackage(Events.SymbolPackagePush, packageId, packageVersion);
411412
}
413+
public void TrackSymbolPackageDeleteEvent(string packageId, string packageVersion)
414+
{
415+
TrackMetricForSymbolPackage(Events.SymbolPackageDelete, packageId, packageVersion);
416+
}
412417

413418
public void TrackSymbolPackagePushFailureEvent(string packageId, string packageVersion)
414419
{

0 commit comments

Comments
 (0)