-
Notifications
You must be signed in to change notification settings - Fork 644
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
Integrate PermissionsService 2.0 with Gallery #5180
Conversation
@@ -414,8 +413,7 @@ public virtual Task<ActionResult> CreatePackagePost() | |||
} | |||
|
|||
// For a new package id verify that the user is allowed to push to the matching namespaces, if any. | |||
var isPushAllowed = ReservedNamespaceService.IsPushAllowed(id, user, out userOwnedNamespaces); | |||
if (!isPushAllowed) | |||
if (!ActionsRequiringPermissions.UploadNewPackageId.TryGetAccountsIsAllowedOnBehalfOf(user, new ActionOnNewPackageContext(id, ReservedNamespaceService), out var accountsAllowedOnBehalfOf)) |
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.
Why TryGetAccountsIsAllowedOnBehalfOf
when out parameter accountsAllowedOnBehalfOf
isn't used? I'd expect this API to be used wherever we need to populate a drop-down of package owners for an account.
Why not ActionsRequiringPermissions.UploadNewPackageId.CheckPermissions(user, scopeOwner, packageContext)
?
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.
Two notes:
1 - I call TryGetAccountsIsAllowedOnBehalfOf
whenever I want to know if an action is allowed on behalf of any account, regardless of whether or not I care about the list of users. CheckPermissions
is for when the caller knows the exact account the action is attempting to be done on behalf of.
2 - The upcoming UI upload PR will fix this flow to actually work as intended with organizations--this is merely a quick syntax replacement of the old broken 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.
The TryParse pattern in the .NET framework is intended for improving performance in what would normally be an exception case. see: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions-and-performance
Looking at the following API, I assume I'm trying to get a list of accounts but that the get operation might throw for some reason.
bool TryGetAccountsIsAllowedOnBehalfOf(user, packageContext, out accountsAllowed);
Whereas the following API looks like it checks if the user has permissions:
bool CheckPermissions(currentUser, owner, package);
You should reconsider the Try API, as it doesn't come across doing what it's intended for. Another sign that the pattern is misused is that the out parameter is often ignored by the callers.
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.
Are you suggesting I rename it to
bool IsAllowedOnBehalfOfAnAccount(user, package, out accountsAllowed)
?
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.
Ideally APIs have a strong contract for callers. If comments/docs are necessary to use the API, then it could be a bad name or poor design (i.e., doing too much).
In this case, it looks like the API was created with two intents:
- Can User X do Action A on behalf of User Y?
AND - Get Users that User X can do Action A on behalf of
If that's the case, then I suggest both rename and splitting this into multiple APIs with a well defined purpose. For instance:
interface IActionRequiringEntityPermissions<TEntity> {
bool IsAllowedOnBehalfOfAnyOwner(User currentUser, TEntity);
bool IsAllowedOnBehalfOfOwner(User currentUser, User entityOwner, TEntity);
List<TEntity> GetAccountsAllowedOnBehalfOf(User currentUser, TEntity);
}
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.
What may also be confusing me is the following public API on IActionRequiringEntityPermission:
PermissionsCheckResult CheckPermissions(User currentUser, User account, TEntity entity);
I had the impression that this API would be used by callers checking permissions, and the other was for callers that needed a list of accounts (i.e, for populating a drop-down). Is CheckPermissions intended to be used by callers?
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 want to split IsAllowedOnBehalfOfAnAccount
into two parts because
1 - the list of accounts is meaningless if the action is not allowed on behalf of any account
2 - when you want both whether or not the action is allowed on behalf of any accounts and the list of the accounts it is allowed on behalf of, splitting the method into two would require the same complex logic to be run twice, which is an unnecessary performance hit
3 - checking if an account is allowed on behalf of any account and the list of those accounts are very closely related
4 - although I agree with you that the earlier name (TryGet
) was confusing, I believe the newer proposition IsAllowedOnBehalfOfAnyAccount
is very clear
5 - ultimately I don't think ignoring an out
parameter is too concerning, although I'd be ok creating a version of the method without the out
parameter while keeping the version with it
So, what I'm thinking of
PermissionsCheckResult CheckPermissions(currentUser, accountOnBehalfOf, entity)
bool IsAllowedOnBehalfOfAnyAccount(currentUser, entity)
bool IsAllowedOnBehalfOfAnyAccount(currentUser, entity, out accountsAllowed)
If having the bool
is too confusing with the `PermissionsCheckResult, I'm also ok with
PermissionsCheckResult CheckPermissions(currentUser, accountOnBehalfOf, entity)
PermissionsCheckResult CheckPermissionsOnBehalfOfAnyAccount(currentUser, entity)
PermissionsCheckResult CheckPermissionsOnBehalfOfAnyAccount(currentUser, entity, out accountsAllowed)
but that would require adding some logic to determine which PermissionsCheckResult
to return when the case is not PermissionsCheckResult.Allowed
(probably max int value of any enum state returned).
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 sounds like CheckPermissions
and IsAllowedOnBehalfOfAnyAccount
are the same except:
- different return value (bool vs. more granular enum)
- check permissions for specific owner, vs any owner
If the purpose is so similar, why are the APIs so different? Could it just be (don't care which retval you use, just be consistent):
PermissionsCheckResult IsAllowedOnBehalfOf(currentUser, accountOnBehalfOf, entity);
PermissionsCheckResult IsAllowedOnBehalfOfAny(currentUser, entity);
The API that gets the list of accounts has a different purpose - getting accounts which pass the permissions check. I expect this to always return at least 1 account (currentUser), and should only be used by callers that need the list of accounts:
List<User> GetAccountsAllowedOnBehalfOf(currentUser, entity);
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 agree with your point on consistency in retval, but as mentioned above, I don't want to make a separate method for getting the list of accounts on behalf of.
I'm planning on the following API:
PermissionsCheckResult CheckPermissions(currentUser, accountOnBehalfOf, entity)
PermissionsCheckResult CheckPermissionsOnBehalfOfAnyAccount(currentUser, entity)
PermissionsCheckResult CheckPermissionsOnBehalfOfAnyAccount(currentUser, entity, out accountsAllowed)
I will open up a PR separate with this one with those changes and then make the changes to this branch retroactively.
@@ -71,6 +72,7 @@ public virtual ActionResult GetPackageOwners(string id, string version) | |||
.Select(u => new PackageOwnersResultViewModel( | |||
u, | |||
currentUser, | |||
packageRegistration, |
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.
Why not do the work (permissions check) in the controller and pass the result to the view model?
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 constructor of PackageOwnersResultViewModel
does the checks and saves the results for the view to use. I didn't want to have to copy all of the permissions checks to every place this view model is used.
return false; | ||
} | ||
|
||
if (!ActionsRequiringPermissions.ManagePackageOwnership.TryGetAccountsIsAllowedOnBehalfOf(currentUser, package, out var accountsAllowedOnBehalfOf)) |
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.
Same comment as above... wondering why TryGetAccountsIsAllowedOnBehalfOf
is used when the out parameter accountsAllowedOnBehalfOf
is not used. Looks like there's more callers with this pattern below too.
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.
see response to "Why TryGetAccountsIsAllowedOnBehalfOf
when out parameter accountsAllowedOnBehalfOf
isn't used?" above
7420c37
to
0c1f6f4
Compare
@scottbommarito Please see my comment about the TryParse pattern, which is relevant for both this PR and #5192 -- #5180 (comment) |
0c1f6f4
to
14976d1
Compare
d80b513
to
0398a08
Compare
I'm fixing this PR right now, sorry for how broken it became! |
d780e1c
to
ccba226
Compare
should be fixed now! |
ccba226
to
cb7294c
Compare
@scottbommarito Looks like a rebase is needed? I'm seeing auditing changes related to the PII work. |
@chenriksson woops...git is AWFUL at feature branches, will fix |
6351706
to
cb7294c
Compare
326975b
to
0c02348
Compare
cb7294c
to
1f7e4bc
Compare
@chenriksson ok it's actually fixed now! |
@@ -414,8 +413,7 @@ public virtual Task<ActionResult> CreatePackagePost() | |||
} | |||
|
|||
// For a new package id verify that the user is allowed to push to the matching namespaces, if any. | |||
var isPushAllowed = ReservedNamespaceService.IsPushAllowed(id, user, out userOwnedNamespaces); | |||
if (!isPushAllowed) | |||
if (ActionsRequiringPermissions.UploadNewPackageId.CheckPermissionsOnBehalfOfAnyAccount(user, new ActionOnNewPackageContext(id, ReservedNamespaceService)) != PermissionsCheckResult.Allowed) |
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
{ | ||
return self == null && user == null; | ||
} | ||
|
||
return self.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.
nit: could simplify to self?.Key == user?.Key
@@ -91,7 +91,7 @@ public bool Equals(User x, User y) | |||
|
|||
public int GetHashCode(User obj) | |||
{ | |||
return obj.Key.GetHashCode(); | |||
return obj == null ? -1 : obj.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.
nit: could simplify to obj?.Key ?? -1
.Setup(r => r.IsPushAllowed(It.IsAny<string>(), It.IsAny<User>(), out matchingNamespaces)) | ||
.Returns(true); | ||
.Setup(s => s.GetReservedNamespacesForId(It.IsAny<string>())) | ||
.Returns(new ReservedNamespace[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: spacing?
@@ -69,7 +69,7 @@ public void ResolvePathIsCorrect() | |||
|
|||
// Act | |||
var result = urlHelper.PackageRegistrationTemplate() | |||
.Resolve(new ListPackageItemViewModel(package)); | |||
.Resolve(new ListPackageItemViewModel(package, 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.
nit: use named parameter for nulls, if possible.
This PR removes the old
PermissionsService
from the gallery and integrates the new permissions service.