Skip to content

Commit daf4172

Browse files
committed
Add specific error message for reservations with no owner
1 parent 990e0fe commit daf4172

16 files changed

Lines changed: 213 additions & 28 deletions

src/GitHubVulnerabilities2Db/Gallery/ThrowingTelemetryService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ public void TrackPackagePushNamespaceConflictEvent(string packageId, string pack
256256
throw new NotImplementedException();
257257
}
258258

259+
public void TrackPackagePushOwnerlessNamespaceConflictEvent(string packageId, string packageVersion, User user, IIdentity identity)
260+
{
261+
throw new NotImplementedException();
262+
}
263+
259264
public void TrackPackageReadMeChangeEvent(Package package, string readMeSourceType, PackageEditReadMeState readMeState)
260265
{
261266
throw new NotImplementedException();

src/NuGetGallery.Services/Permissions/ActionRequiringReservedNamespacePermissions.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,21 @@ protected override PermissionsCheckResult CheckPermissionsForEntity(User account
5757
return PermissionsCheckResult.Allowed;
5858
}
5959

60+
var hasAnyOwners = reservedNamespaces.Any(rn => rn.Owners.Any());
61+
6062
// Permissions on only a single namespace are required to perform the action.
61-
return reservedNamespaces.Any(rn => PermissionsHelpers.IsRequirementSatisfied(ReservedNamespacePermissionsRequirement, account, rn)) ?
62-
PermissionsCheckResult.Allowed : PermissionsCheckResult.ReservedNamespaceFailure;
63+
if (reservedNamespaces.Any(rn => PermissionsHelpers.IsRequirementSatisfied(ReservedNamespacePermissionsRequirement, account, rn)))
64+
{
65+
return PermissionsCheckResult.Allowed;
66+
}
67+
else if (hasAnyOwners)
68+
{
69+
return PermissionsCheckResult.ReservedNamespaceFailure;
70+
}
71+
else
72+
{
73+
return PermissionsCheckResult.OwnerlessReservedNamespaceFailure;
74+
}
6375
}
6476

6577
public PermissionsCheckResult CheckPermissionsOnBehalfOfAnyAccount(User currentUser, ActionOnNewPackageContext newPackageContext)

src/NuGetGallery.Services/Permissions/PermissionsCheckResult.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public enum PermissionsCheckResult
3131
/// <summary>
3232
/// The current user does not have permissions to perform the action on the <see cref="ReservedNamespace"/> on behalf of another <see cref="User"/>.
3333
/// </summary>
34-
ReservedNamespaceFailure
34+
ReservedNamespaceFailure,
35+
36+
/// <summary>
37+
/// The current user does not have permissions to perform the action on the <see cref="ReservedNamespace"/> on behalf of another <see cref="User"/>
38+
/// but none of the namespaces currently have owners.
39+
/// </summary>
40+
OwnerlessReservedNamespaceFailure,
3541
}
3642
}

src/NuGetGallery.Services/Telemetry/ITelemetryService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ void TrackPackageDeprecate(
6161

6262
void TrackPackagePushNamespaceConflictEvent(string packageId, string packageVersion, User user, IIdentity identity);
6363

64+
void TrackPackagePushOwnerlessNamespaceConflictEvent(string packageId, string packageVersion, User user, IIdentity identity);
65+
6466
void TrackVerifyPackageKeyEvent(string packageId, string packageVersion, User user, IIdentity identity, int statusCode);
6567

6668
void TrackNewUserRegistrationEvent(User user, Credential identity);

src/NuGetGallery.Services/Telemetry/TelemetryService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class Events
2828
public const string VerifyPackageKey = "VerifyPackageKey";
2929
public const string PackageReadMeChanged = "PackageReadMeChanged";
3030
public const string PackagePushNamespaceConflict = "PackagePushNamespaceConflict";
31+
public const string PackagePushOwnerlessNamespaceConflict = "PackagePushOwnerlessNamespaceConflict";
3132
public const string NewUserRegistration = "NewUserRegistration";
3233
public const string CredentialAdded = "CredentialAdded";
3334
public const string CredentialUsed = "CredentialUsed";
@@ -364,6 +365,11 @@ public void TrackPackagePushNamespaceConflictEvent(string packageId, string pack
364365
TrackMetricForPackage(Events.PackagePushNamespaceConflict, packageId, packageVersion, user, identity);
365366
}
366367

368+
public void TrackPackagePushOwnerlessNamespaceConflictEvent(string packageId, string packageVersion, User user, IIdentity identity)
369+
{
370+
TrackMetricForPackage(Events.PackagePushOwnerlessNamespaceConflict, packageId, packageVersion, user, identity);
371+
}
372+
367373
public void TrackCreatePackageVerificationKeyEvent(string packageId, string packageVersion, User user, IIdentity identity)
368374
{
369375
TrackMetricForPackage(Events.CreatePackageVerificationKey, packageId, packageVersion, user, identity);

src/NuGetGallery/Controllers/ApiController.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,11 @@ private HttpStatusCodeWithBodyResult GetHttpResultFromFailedApiScopeEvaluationHe
11951195
TelemetryService.TrackPackagePushNamespaceConflictEvent(id, versionString, GetCurrentUser(), User.Identity);
11961196
return new HttpStatusCodeWithBodyResult(HttpStatusCode.Conflict, Strings.UploadPackage_IdNamespaceConflict);
11971197
}
1198+
else if (result.PermissionsCheckResult == PermissionsCheckResult.OwnerlessReservedNamespaceFailure)
1199+
{
1200+
TelemetryService.TrackPackagePushOwnerlessNamespaceConflictEvent(id, versionString, GetCurrentUser(), User.Identity);
1201+
return new HttpStatusCodeWithBodyResult(HttpStatusCode.Conflict, Strings.UploadPackage_OwnerlessIdNamespaceConflict);
1202+
}
11981203

11991204
var message = result.PermissionsCheckResult == PermissionsCheckResult.Allowed && !result.IsOwnerConfirmed ?
12001205
Strings.ApiKeyOwnerUnconfirmed : Strings.ApiKeyNotAuthorized;

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -507,15 +507,29 @@ private async Task<JsonResult> UploadPackageInternal(PackageArchiveReader packag
507507
var id = nuspec.GetId();
508508
existingPackageRegistration = _packageService.FindPackageRegistrationById(id);
509509
// For a new package id verify if the user is allowed to use it.
510-
if (existingPackageRegistration == null &&
511-
ActionsRequiringPermissions.UploadNewPackageId.CheckPermissionsOnBehalfOfAnyAccount(
512-
currentUser, new ActionOnNewPackageContext(id, _reservedNamespaceService), out accountsAllowedOnBehalfOf) != PermissionsCheckResult.Allowed)
510+
if (existingPackageRegistration == null)
513511
{
514-
var version = nuspec.GetVersion().ToNormalizedString();
515-
_telemetryService.TrackPackagePushNamespaceConflictEvent(id, version, currentUser, User.Identity);
512+
var result = ActionsRequiringPermissions.UploadNewPackageId.CheckPermissionsOnBehalfOfAnyAccount(
513+
currentUser,
514+
new ActionOnNewPackageContext(id, _reservedNamespaceService),
515+
out accountsAllowedOnBehalfOf);
516516

517-
return Json(HttpStatusCode.Conflict, new[] {
518-
new JsonValidationMessage(string.Format(CultureInfo.CurrentCulture, Strings.UploadPackage_IdNamespaceConflict)) });
517+
if (result == PermissionsCheckResult.ReservedNamespaceFailure)
518+
{
519+
var version = nuspec.GetVersion().ToNormalizedString();
520+
_telemetryService.TrackPackagePushNamespaceConflictEvent(id, version, currentUser, User.Identity);
521+
return Json(HttpStatusCode.Conflict, new[] { new JsonValidationMessage(Strings.UploadPackage_IdNamespaceConflict) });
522+
}
523+
else if (result == PermissionsCheckResult.OwnerlessReservedNamespaceFailure)
524+
{
525+
var version = nuspec.GetVersion().ToNormalizedString();
526+
_telemetryService.TrackPackagePushOwnerlessNamespaceConflictEvent(id, version, currentUser, User.Identity);
527+
return Json(HttpStatusCode.Conflict, new[] { new JsonValidationMessage(new OwnerlessNamespaceIdConflictMessage()) });
528+
}
529+
else if (result != PermissionsCheckResult.Allowed)
530+
{
531+
throw new InvalidOperationException($"When checking if a new package ID can be created, result '{result}' is not expected.");
532+
}
519533
}
520534

521535
// For existing package id verify if it is owned by the current user
@@ -2577,9 +2591,13 @@ protected virtual async Task<JsonResult> VerifyPackageInternal(
25772591
// The owner specified in the form is not allowed to push to a reserved namespace matching the new ID
25782592
var version = packageVersion.ToNormalizedString();
25792593
_telemetryService.TrackPackagePushNamespaceConflictEvent(packageId, version, currentUser, User.Identity);
2580-
2581-
var message = string.Format(CultureInfo.CurrentCulture, Strings.UploadPackage_IdNamespaceConflict);
2582-
return Json(HttpStatusCode.Conflict, new[] { new JsonValidationMessage(message) });
2594+
return Json(HttpStatusCode.Conflict, new[] { new JsonValidationMessage(Strings.UploadPackage_IdNamespaceConflict) });
2595+
}
2596+
else if (checkPermissionsOfUploadNewId == PermissionsCheckResult.OwnerlessReservedNamespaceFailure)
2597+
{
2598+
var version = packageVersion.ToNormalizedString();
2599+
_telemetryService.TrackPackagePushOwnerlessNamespaceConflictEvent(packageId, version, currentUser, User.Identity);
2600+
return Json(HttpStatusCode.Conflict, new[] { new JsonValidationMessage(new OwnerlessNamespaceIdConflictMessage()) });
25832601
}
25842602

25852603
// An unknown error occurred.

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@
308308
<Compile Include="Services\IPackageVulnerabilitiesService.cs" />
309309
<Compile Include="Services\IPackageMetadataValidationService.cs" />
310310
<Compile Include="Services\MarkdownService.cs" />
311+
<Compile Include="Services\OwnerlessNamespaceIdConflictMessage.cs" />
311312
<Compile Include="Services\PackageVulnerabilitiesService.cs" />
312313
<Compile Include="Services\PackageMetadataValidationService.cs" />
313314
<Compile Include="Services\ConfigurationIconFileProvider.cs" />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace NuGetGallery
5+
{
6+
/// <summary>
7+
/// Represents a package ID reservation conflict, but all of the namespaces that prevented the upload have no
8+
/// owners.
9+
/// </summary>
10+
public class OwnerlessNamespaceIdConflictMessage : IValidationMessage
11+
{
12+
public bool HasRawHtmlRepresentation => true;
13+
public string PlainTextMessage => Strings.UploadPackage_OwnerlessIdNamespaceConflict;
14+
public string RawHtmlMessage => Strings.UploadPackage_OwnerlessIdNamespaceConflictHtml;
15+
}
16+
}

src/NuGetGallery/Strings.Designer.cs

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

0 commit comments

Comments
 (0)