-
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
Use PackagePermissionsService to determine actions a user can perform on a package #4923
Conversation
{ | ||
public static class PackagePermissionsService | ||
{ | ||
public enum PermissionLevel |
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 don't think should not be nested. It makes the call sites too verbose.
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.
done
|
||
yield return Permission.ReportMyPackage; | ||
|
||
goto case PermissionLevel.SiteAdmin; |
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: can we write this without goto
?
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, but it will be significantly more verbose
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.
verbose is better IMO for something so critical as permissions.
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.
Which is why I made the tests very explicit. If someone wants to change the permissions they will need to change the tests in the exact same way.
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.
Implementation should be as clear or clearer than tests. People trying to understand our code should not have to dig through unit tests for comprehension. Alternative is to have docs concerning this matrix of permissions.
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.
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.
can we just drop the goto and let it fall through? it looks like that is bascially what is happening here anyway?
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.
C# does not allow falling through, which is why this exists as is
regardless, I rewrote it to be explicit, as @joelverhagen requested
{ | ||
public enum PermissionLevel | ||
{ | ||
None, |
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.
Perhaps this could be call Anonymous
-- this enum almost role
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.
IMO None
makes more sense because they have "no" permissions with the package
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.
Thought about it more and I agree that Anonymous
is better
case PermissionLevel.OrganizationCollaborator: | ||
|
||
yield return Permission.DisplayMyPackage; | ||
yield return Permission.Upload; |
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.
How is the notion of UploadNewVersion
captured? Is that out of scope for this particular PR?
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 should be renamed to UploadNewVersion
. These permissions only affect existing packages.
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.
Aren't Upload
and UploadNewVersion
different permissions tho?
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, but UploadNewId
won't come into play until the user can choose the owner of the new package. These permissions are "I have this existing package, what can I do to it."
E.g. out of scope for this PR
new [] | ||
{ | ||
PackagePermissionsService.Permission.DisplayMyPackage, | ||
PackagePermissionsService.Permission.Upload, |
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 is new for SiteAdmin
right?
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, good catch.
I thought it makes sense, but I'm fine removing the permission
PackagePermissionsService.Permission.DisplayMyPackage, | ||
PackagePermissionsService.Permission.Upload, | ||
PackagePermissionsService.Permission.Edit, | ||
PackagePermissionsService.Permission.Delete |
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.
super nit: trailing ,
so that adding new permissions only effects on line (easier to read diff)
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.
done
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.
⌚️
@@ -392,7 +392,7 @@ public virtual JsonResult UploadPackageProgress() | |||
if (package == null | |||
|| ((package.PackageStatusKey == PackageStatus.Validating | |||
|| package.PackageStatusKey == PackageStatus.FailedValidation) | |||
&& !package.IsOwnerOrAdmin(User))) | |||
&& !PackagePermissionsService.HasPermission(package, User, Permission.DisplayMyPackage))) |
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.
DisplayMyPackage isn't the best name here. Consider: DisplayHiddenPackage/DisplayPrivatePackage
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.
using DisplayPrivatePackage
@@ -1136,7 +1136,7 @@ public virtual Task<ActionResult> RejectPendingOwnershipRequest(string id, strin | |||
} | |||
|
|||
var user = GetCurrentUser(); | |||
if (package.IsOwner(user)) | |||
if (package.Owners.Any(owner => owner.Key == user.Key)) |
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.
if (package.Owners.Any(owner => owner.Key == user.Key)) [](start = 10, length = 57)
This should be handled by the PackagePermissionsService . We will need to add Org logic here soon, so it doesn't make sense to have to logic here
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.
correct, didn't notice this
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.
upon looking over this case, this case shouldn't be handled by the PackagePermissionsService
, because this is for adding a user as a co-owner
I didn't review the remainder of the code and did find some cases that used Owners.Any
directly that should be using PackagePermissionsService
however
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.
upon looking over this case, this case shouldn't be handled by the PackagePermissionsService, because this is for adding a user as a co-owner
- we are handling co-ownership for packages, why shouldnt this be handled by PackagePermissionsService?
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.
yea i mean it didn't use HasPermission
, but it uses GetPermissionLevels
which used to be private
OrganizationCollaborator | ||
} | ||
|
||
public enum Permission |
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 is not Permission, this is Action
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.
changed
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.
🕐
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 should probably also use this to hide the Manage Package Owners button in the Manage Packages page for packages that users are collaborators on.
I don't think those use the _ListPackage.cshtml
public static class PackagePermissionsService | ||
{ | ||
private static readonly IDictionary<PermissionLevel, IEnumerable<Action>> _allowedActions = | ||
new Dictionary<PermissionLevel, IEnumerable<Action>> |
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 is beautiful
private int _key = 0; | ||
|
||
[Flags] | ||
public enum ReturnsExpectedPermissionLevels_State |
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.
👍
@ryuyu Manage Packages uses |
public enum PackageAction | ||
{ | ||
DisplayPrivatePackage, | ||
UploadNewVersion, |
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.
UploadNewVersion is different then uploading a new package id?
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, these are all actions on an existing package or id
UploadNewId
is a right on an organization or a reserved namespace, not an existing package.
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 don't see an "UploadNewId" Action here
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.
It will come in a future PR
DisplayPrivatePackage, | ||
UploadNewVersion, | ||
Edit, | ||
Delete, |
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.
Delete [](start = 8, length = 6)
Delete is actually Unlist?
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...the UI and API endpoints are named Delete. I can add 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.
IMO it's better to rename to Unlist, cause an SiteAdmin can actually do a Delete.
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.
renamed
} | ||
|
||
public enum PackageAction | ||
{ |
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.
PermissionLevel and PackageAction should be extracted into separate files.
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.
done
return GetPermissionLevels( | ||
owners, | ||
principal.IsAdministrator(), | ||
u => UserMatchesPrincipal(u, principal)); |
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: why not just pass principal to the core GetPrincipalLevels. One less arg, and no need for allocating the lambda.
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.
Not sure what you mean exactly...are you suggesting UserMatchesPrincipal
and UserMatchesUser
should return a Func<User, bool>
?
{ | ||
PermissionLevel.Anonymous, | ||
new PackageAction[0] | ||
}, |
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: why include anonymous? HasPermissions could just check, and return false immediately w/o linq.
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.
not possible because I wanted to separate the allowed actions from the permission levels
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.
correction: if we ever decide Anonymous
users can do actions, such as ReportAbuse
, they would need state here
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.
Just saying if anonymous has no actions, no need for the extra allocations... in case this is on the fast path. For instance:
private static bool IsActionAllowed(IEnumerable<PermissionLevel> permissionLevels, PackageAction action)
{
return permissionLevels != null && permissionLevels.Any(permissionLevel => _allowedActions[permissionLevel].Contains(action));
}
public static IEnumerable<PermissionLevel> GetPermissionLevels(IEnumerable<User> owners, IPrincipal principal)
{
if (principal == null)
{
return null;
}
...
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.
Ah, well I think I prefer it like this because
1 - it's easier to extend and add actions to anonymous if we decide we need to
2 - it matches the structure of the rest of the permission levels
ReportMyPackage, | ||
} | ||
|
||
public static class PackagePermissionsService |
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: Is a static class really a service? Wondering if this class should be shortened to just PackagePermissions?
Also, 'Permissions' is redundant. Maybe 'PackagePermissionsService.HasPermissions(...)' could change to 'PackagePermissions.ActionAllowed(...)' or something.
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.
AFAIK it fits all the definitions of being a service, despite being a static class.
Will change to ActionAllowed
return permissionLevels.Any(permissionLevel => _allowedActions[permissionLevel].Contains(action)); | ||
} | ||
|
||
public static IEnumerable<PermissionLevel> GetPermissionLevels(Package package, User user) |
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 GetPermissionLevels APIs seem like they should be private. The only use outside this class is a check if the user is the owner. Maybe you instead expose a single IsOwner API?
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 like them being public, because I think there's a pretty high likelihood we will use them to check other states besides IsOwner
in the future.
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.
Just a suggestion, but I think it opens up complexity to the callers that this service was intended for.
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 replaces
ExtensionMethods.IsOwner
.I went through all previous calls of
IsOwner
and replaced them with aHasPermissions
call with the rightPermission
. I also added checks to_UserPackagesList.cshtml
to only show actions that the user can perform.