-
Notifications
You must be signed in to change notification settings - Fork 645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow organization admins to remove their organization's ownership of a package in a reserved namespace #5984
Conversation
@@ -15,7 +15,7 @@ namespace NuGetGallery | |||
/// E.g. "JQuery.Extensions.MyCoolExtension" matches both "JQuery.*" and "JQuery.Extensions.*". | |||
/// </remarks> | |||
public class ActionRequiringReservedNamespacePermissions | |||
: ActionRequiringEntityPermissions<IReadOnlyCollection<ReservedNamespace>>, IActionRequiringEntityPermissions<ActionOnNewPackageContext> | |||
: ActionRequiringEntityPermissions<IReadOnlyCollection<ReservedNamespace>>, IActionRequiringEntityPermissions<ActionOnNewPackageContext>, IActionRequiringEntityPermissions<ReservedNamespace> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: wrap long lines; makes it easier to review
|
||
private IReadOnlyCollection<ReservedNamespace> GetReservedNamespaces(ReservedNamespace reservedNamespace) | ||
{ | ||
return new[] { reservedNamespace }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there another way around this? Would prefer to avoid unnecessary allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, this system expects an array of reserved namespaces so we have to convert a single namespace into an array
/// </summary> | ||
public static ActionRequiringReservedNamespacePermissions ShowPackageAsVerifiedByReservedNamespace = | ||
new ActionRequiringReservedNamespacePermissions( | ||
accountOnBehalfOfPermissionsRequirement: PermissionsRequirement.Owner, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first glance, this readonly permission seemed more restrictive than the Remove permission below... but seems like that's because we only invoke this on package owners. Maybe it's worth calling this out in the comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, this action is
If I own a package, I can show that package as verified by a reserved namespace that applies to it.
The reserved namespace must be owned by you and exactly you (you cannot show your package as verified on behalf of another user that owns a reserved namespace that applies to it), so the permissions requirements here are both Owner
. It's a little convoluted but by using ActionsRequiringPermissions
for this purpose we shouldn't have issues similar to what we saw here in the future.
transaction.Commit(); | ||
} | ||
} | ||
else | ||
{ | ||
await RemovePackageOwnerImplAsync(packageRegistration, requestingOwner, ownerToBeRemoved); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We verify that requestingOwner
has permissions for ownerToBeRemoved
(organization) higher in the stack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we call OwnerHasPermissionsToRemove
earlier which does the verification.
{ | ||
return packageRegistration.ReservedNamespaces.Any(rn => rn.Owners.Any(owner => owner == user)); | ||
// If multiple package owners own a reserved namespace that the package is a member of, then removing an owner will not remove the package from all of its reserved namespaces. | ||
// If no package owners own a reserved namespace that the package is a member of, then the package is not in a reserved namespace and the owner can be removed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is confusing, essentially if there are no reserved namespaces associated with a package, then the owner can be removed would be the simplest way to state it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is trying to explain how the number of package owners owning a reserving namespace is related to the package being in a reserved namespace...I think your comment removes this relationship. I'm not sure how to make the connection and also not have a confusing comment here.
return packageRegistration.ReservedNamespaces.Any(rn => rn.Owners.Any(owner => owner == user)); | ||
// If multiple package owners own a reserved namespace that the package is a member of, then removing an owner will not remove the package from all of its reserved namespaces. | ||
// If no package owners own a reserved namespace that the package is a member of, then the package is not in a reserved namespace and the owner can be removed. | ||
return packageOwnersThatOwnAReservedNamespaceThatThePackageIsAMemberOf != 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this method is missing a case, you aren't checking the namespace ownership of ownerToBeRemoved
, if the requestingOwner
doesn't own the namespace, and neither does the ownerToBeRemoved
, and there two other owners who do own the namespace, this method will return false
; which it shouldn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep it simple like the previous way, and check for the ownerships(of course with OnBehalfOf
) of both owners to derive logical conclusion on permission for removal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally correct. The reason why this wasn't broken in my unit tests was because of the !=
(instead of a ==
) in line 203. I have fixed and simplified the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are some missing checks for owner removal.
/// <summary> | ||
/// The action of showing a package as verified by a reserved namespace. | ||
/// </summary> | ||
public static ActionRequiringReservedNamespacePermissions ShowPackageAsVerifiedByReservedNamespace = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a misnomer for the action. Rather rename it as IsOwnerOfReservedNamespace
or a better action that represents ownership of namespace than the ShowPackageAsVerified
, its not really the application of this action in the code below.
#5944
1 - Organization A owns package B that is in reserved namespace C
2 - User D that is admin of A removes A from ownership of B
Expected:
Organization A is no longer an owner of B and B loses its verification
Actual:
D gets an HTTP 500.
Organization A remains an owner of B.
This PR fixes that behavior.
Also made some general fixes to prevent issues like these in the future.
E.g. we now use
ActionsRequiringPermissions
to determine reserved namespace package verification.