Skip to content
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

Merged
merged 19 commits into from
Oct 27, 2017

Conversation

scottbommarito
Copy link
Contributor

This replaces ExtensionMethods.IsOwner.

I went through all previous calls of IsOwner and replaced them with a HasPermissions call with the right Permission. I also added checks to _UserPackagesList.cshtml to only show actions that the user can perform.

{
public static class PackagePermissionsService
{
public enum PermissionLevel
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please no goto in code


In reply to: 147482420 [](ancestors = 147482420)

Copy link
Contributor

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?

Copy link
Contributor Author

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,
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Contributor Author

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;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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,
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@joelverhagen joelverhagen left a 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)))
Copy link
Contributor

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

Copy link
Contributor Author

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))
Copy link
Contributor

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

Copy link
Contributor Author

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

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Copy link
Contributor

@skofman1 skofman1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

Copy link
Contributor

@ryuyu ryuyu left a 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>>
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@scottbommarito
Copy link
Contributor Author

scottbommarito commented Oct 27, 2017

@ryuyu Manage Packages uses _UserPackagesList, which is handled by these changes

public enum PackageAction
{
DisplayPrivatePackage,
UploadNewVersion,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed

}

public enum PackageAction
{
Copy link
Member

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.

Copy link
Contributor Author

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));
Copy link
Member

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.

Copy link
Contributor Author

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]
},
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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

Copy link
Member

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;
    }
    ...

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor

@skofman1 skofman1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@scottbommarito scottbommarito merged commit e34a93e into dev Oct 27, 2017
@scottbommarito scottbommarito deleted the sb-isowner branch October 27, 2017 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants