-
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
Support for policy propagation #4061
Conversation
{ | ||
if (string.IsNullOrEmpty(id)) | ||
{ | ||
throw new ArgumentNullException(nameof(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.
ArgumentNullException [](start = 26, length = 21)
ArgumentException since if id is empty ArgumentNullException will be confusing
var package = _packageService.FindPackageRegistrationById(id); | ||
if (package == null) | ||
{ | ||
return Json(new { success = false, message = "Package not found" }); | ||
return Task.FromResult(Json(new { success = false, message = "Package not found." })); |
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.
messages should be in Strings.resx
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, except for existing and new emails in MessageService
@@ -206,10 +206,13 @@ protected override void Load(ContainerBuilder builder) | |||
.AsSelf() | |||
.As<ISecurityPolicyService>() | |||
.InstancePerLifetimeScope(); | |||
|
|||
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.
Empty white space here.
success: function (data) { | ||
if (data.success) { | ||
if (data.confirmation) { | ||
if (!confirm(data.confirmation)) { |
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.
Merge this with the previous conditional?
@@ -11,7 +10,7 @@ namespace NuGetGallery.Security | |||
/// </summary> | |||
public class RequirePackageVerifyScopePolicy : UserSecurityPolicyHandler | |||
{ | |||
public const string PolicyName = "RequirePackageVerifyScopePolicy"; | |||
public const string PolicyName = nameof(RequirePackageVerifyScopePolicy); |
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.
👍
policyMessage = SecurePushMessages.NoticeOfPoliciesSubscribedByPolicyOwner(policyMessageOwners, user); | ||
} | ||
} | ||
|
||
ConfirmOwnershipResult result = await _packageService.ConfirmPackageOwnerAsync(package, user, token); |
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.
token verification might fail. This will happened only in ConfirmPackageOwnerAsync method. With the current code this will be after subscription propagation was applied.
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.
Thanks - good catch!
|
||
{PolicyDescriptions} | ||
|
||
Note this step cannot be easily undone. If you are unsure and/or need more information, please contact support@nuget.org. |
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.
Need to update to use 'Gallery.GalleryOwner' for support@nuget.org.
} | ||
if (!package.IsOwner(HttpContext.User)) | ||
|
||
var propagatingOwners = package.Owners.Where(o => RequireSecurePushForCoOwnersPolicy.IsSubscribed(o)).Select(o => o.Username); |
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: This can be Where(RequireSecurePushForCoOwnersPolicy.IsSubscriptedTo)
. If you change this line, there are a few more in this pull request that you can update as well.
83bf7cc
to
f2defca
Compare
{ | ||
foreach (var owner in package.Owners) | ||
{ | ||
if (await SubscribeToSecurePushAsync(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.
Is this likely to fail? If so, how would we find and then add missing secure push policies? Would it be better to move SubscriptToSecurePushAsync
to the PackageService
's AddPackageOwnerAsync
, and if SubscriptToSecurePushAsync
fails for any of the owners, then the overall AddPackageOwnerAsync
operation fails.
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 Loic here. Consider spliting ConfirmPackageOwnerAsync to two methods: one to evaluate the token and return true/false, and another to actually apply the ownership. HandleNewPackageOwnerAsync can run after verifications of ownership but before actually applying.
In reply to: 120957931 [](ancestors = 120957931)
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 do; was trying to avoid modifying the existing PackageService.ConfirmPackageOwnerAsync which is committed in a separate transaction.
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 can still keep it in a separate transaction, it's just better to fail adding the owner (and the user can retry that), then to fail adding policies.
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.
On a related note, if a user is subscribed to the policy as the result of adding a new owner, but adding the owner eventually fails, I think we should unsubscribe that use from the policy.
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.
@loic-sharma @skofman1 @scottbommarito Code was updated to validate the owner request token before applying policy subscription(s). I didn't add logic to unsubscribe should SQL to add owner fail. This is currently hard since there are multiple points of failure: add owner transaction, remove owner request transaction, auditing. Failing more gracefully is probably something we can generally do better, but I think the current behavior here is sufficient for now.
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.
Need to handle the case when subscribing fails.
{ | ||
return Json(new { success = false, message = string.Format(CultureInfo.InvariantCulture, "Sorry, {0} hasn't verified their email account yet and we cannot proceed with the request.", username) }); | ||
var propagatingOwners = package.Owners.Where(RequireSecurePushForCoOwnersPolicy.IsSubscribed).Select(o => o.Username); |
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 code appears extremely similar to the code above in GetAddPackageOwnerConfirmationMessage
, but the message emitted is different. I would suggest moving it to a helper method that returns the list of propagating owners and propagating pending owners.
This would also improve the readability of the code, because it will be immediately obvious what the code is doing--finding the propagating users.
} | ||
|
||
[HttpPost] | ||
public async Task<JsonResult> RemovePackageOwner(string id, string username) | ||
private Task<JsonResult> ManagePackageOwnerAsync(string id, string username, Func<PackageRegistration, User, User, Task<JsonResult>> actionAsync) |
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 the code would be much more readable if you changed this design to remove the delegates.
I would suggest the Try-
model.
For example:
old
return ManagePackageOwnerAsync(id, user, func);
new:
var canManageOwners = TryManagePackageOwners(id, username, out var jsonResult, out var package, out var newOwner, out var currentOwner);
if (!canManageOwners)
{
return jsonResult;
}
return func(package, newOwner, currentOwner);
{ | ||
foreach (var owner in package.Owners) | ||
{ | ||
if (await SubscribeToSecurePushAsync(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.
On a related note, if a user is subscribed to the policy as the result of adding a new owner, but adding the owner eventually fails, I think we should unsubscribe that use from the policy.
@@ -26,7 +26,13 @@ public interface ISecurityPolicyService | |||
/// <summary> | |||
/// Subscribe a user to one or more security policies. | |||
/// </summary> | |||
Task SubscribeAsync(User user, IUserSecurityPolicySubscription subscription); | |||
/// <returns></returns> |
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: get rid of this empty <returns></returns>
var model = ResultAssert.IsView<PackageOwnerConfirmationModel>(result); | ||
Assert.Equal(confirmationResult, model.Result); | ||
Assert.Equal("foo", model.PackageId); | ||
} | ||
|
||
[Fact] | ||
public async Task SubscribesOwnersToSecurePushAndSendsEmailIfNewOwnerRequires() |
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 would suggest putting these inside a TestContainer
so that these tests are separated from the rest of the TheConfirmOwnerMethod
tests, because they are specifically related to RequireSecurePushForCoOwnersPolicy
.
} | ||
|
||
[Fact] | ||
public async Task SubscribesNewOwnerToSecurePushAndSendsEmailIfOwnerRequires() |
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 need failure state tests:
- subscribing new owner fails
- new owner should not be added
- old owners shouldn't be subscribed if they weren't before
- subscribing old owners fails
- new owner should not be added
- new owner and other old owners shouldn't be subscribed if they weren't before
Still looking into the following:
|
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.
looks awesome!
private User FindUser(string username) | ||
{ | ||
return EntitiesContext.Users | ||
.FirstOrDefault(u => username.Equals(u.Username, StringComparison.OrdinalIgnoreCase)); |
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.
Does EF properly understand the string.Equals
call?..
I mean, if the server side collation settings are case sensitive, would this properly do the case insensitive comparison?
If not, then this code is confusing and should be replaced with a simple ==
.
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's worked so far, but likely haven't tested against case sensitive collation. I verified that AuthenticationService is using '==', so I'll update to match.
{ | ||
foreach (var subscription in PolicyService.UserSubscriptions) | ||
var user = FindUser(r.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.
Check r.Key
for null before calling the FindUser
?
(and another instance few lines below)
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.
Also, FindUser
will return null
if the user doesn't exist. Should this case be handled?
{ | ||
if (string.IsNullOrEmpty(subscriptionName)) | ||
{ | ||
throw new ArgumentException(nameof(subscriptionName)); |
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.
ArgumentNullException
?
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.
Per @skofman1 I should throw ArgException for IsNullOrEmpty. I'll update the other APIs that take subscriptionName to throw ArgException for consistency.
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.
Maybe check using the IsNullOrWhitespace
then?
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.
Left as IsNullOrEmpty since it's used most consistently in the Gallery. Maybe we should revisit throughout at some point.
{ | ||
foreach (var subscription in PolicyService.UserSubscriptions) | ||
var user = FindUser(r.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.
Also, FindUser
will return null
if the user doesn't exist. Should this case be handled?
2bd0946
to
7afc8df
Compare
- Added inline confirmation when adding new package owner - Added package URL link to package owner request emails - Added new notification to co-owners when package owner request is confirmed - Added secure push policy messaging to communication above (confirmation, request, and notification) - Added secure push policy messaging to package view for owners and admins - Fixed bug on security policy admin view where toggle all broken if multiple subscriptions - Updated security policy admin view to not reload page on update postback
* Fixed Report Abuse Page's Accessibility (#4001) Fixes #4002. Relevant to [VSTS #395879](https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=395879&_a=edit). This help-text bug was for the sign-in page, but this also affects the Report Abuse page. * Fixed accessibility of email field in report abuse page * Fixed signature accessibility * Use ServerCommon's Response Code Processor (#3999) * Added Nuget.Services.Logging; moved to TelemetryResponseCodeProcessor * Added binding redirect for AI * Removing the "WITH(ONLINE=ON)" from the index creation as per #3952 (#4004) * Removing the "WITH(ONLINE=ON)" from the index creation as per #3952 (#4004) (#4030) * Remove validation rules that block uploading valid semver2 versions #3645 (#3757) * Add support for semVerLevel query parameter to V2 endpoints (#3714) * adding new optional semVerLevel query parameter to v2 odata endpoints * adding new optional semVerLevel query parameter to v2 autocomplete endpoints * Applying semVerLevel filter on v2 OData endpoints * Use [FromUri] attribute on semVerLevel (avoids having single quotes in the parameter value) * Ensure navigation links on v2 feeds use normalized version * Clarifying comment on Get(Id=,Version=) v2 API * Properly default to semver2 inclusion on Get(Id=,Version=) * Compare NormalizedVersion to be able to retrieve matching SemVer2 package versions for a given normalized version string. * Code review feedback * Update and fix broken test data * Keep legacy version compliance checks in place for non-SemVer2 versions (#3761) * Keep legacy version compatibility checks in place for non-semver2 versions * Added comment to clarify the reasons behind the legacy version check. * Fix typo * Rename test for clarity * code review feedback * Set SemVerLevelKey after setting Dependencies * #3861 V2 NuGetEntityTypeSerializer Id link patcher must retain curated feed name (#3864) * Support Is(Absolute)Latest for SemVer2 + semVerLevel for SearchService (#3842) * LuceneIndexingService in Gallery should take into account IsLatest(Stable)SemVer2 (#3863) * POST VerifyPackage version validation should use ToFullString comparison * ODataV2CuratedFeedController should support semver2 by default when requesting a specific version, and compare on NormalizedVersion * Refactor NuGetEntityTypeSerializer + unit test coverage (#3879) * Use NormalizedVersion in URLs contained in PackageAddedNotice (#3886) * Add nullcheck and use TryParse. (#3890) * Show full version on package details page (#3887) * Highlight semver2 packages on package details view (#3893) * Fix bug in IsLatest(Stable)SemVer2 (#3895) * VerifyPackage on ApiController should treat version as optional parameter (#3903) * UpdateIsLatest not resetting IsLatest(Stable)SemVer2 on previous latest versions (#3909) * Fix malformed URL in redirect after package upload (#3915) * Minor fix for search results package URLs (when to use version or not in the URL) * UrlHelper extension for Package should use NormalizedVersion (#3925) * Default to latest stable semver2 on package details page (#3930) * User profile page does not show SemVer 2.0.0 packages #3911 (#3933) * Fix functional test failure SearchMicrosoftDotNetCuratedFeed #3941 (#3942) * Fix System.NotSupportedException on User profile page (#3943) * Fix Functional Test failure for ODataFeeds.V2FeedExtendedTests.FindPackagesByIdTest #3947 (#3948) * Fix load test failure due to incorrect test setup (#3957) * Hijack IsLatest(Stable)Version OData filter when semVerLevel=2.0.0 (#3966) * Detect if package only differ by metadata and show optimal user-facing error message (#3970) * Update Semver2 package details message with final nuget client version #3897 (#3988) * On package validation failure an actionable error message should be displayed. #3916 (#4031) * Make downloads link on home page a proper link (#4052) * Fix the date format on stats page (#4057) * Update telemetry processors (#4059) * Reorder SemVer2Latest migration to match deployment history (#4062) * Average download shown incorrectly when its 1.x #4039 (#4040) * Average download shown incorrectly when its 1.x #4039 * Moved logic to viewmodel and added UTs * SemVer2 - Missing db index on Packages table #498 (#4073) * SemVer2 - Missing db index on Packages table #498 * SemVer2 - Missing db index on Packages table for partial search #499 (#4074) * Package-Versions autocomplete endpoint does not properly handle semVerLevel when using the db #4086 (#4087) * Package-Versions autocomplete endpoint does not properly handle semVerLevel when using the db #4086 (#4087) * v2 package-versions auto-complete endpoint should exclude deleted versions #4092 (#4093) * Remove auto-refresh AJAX call for total stats on home page #4090 (#4091) * v2 package-versions auto-complete endpoint should exclude unlisted versions #4092 (#4099) * Support for policy propagation (#4061) - Added inline confirmation when adding new package owner - Added package URL link to package owner request emails - Added new notification to co-owners when package owner request is confirmed - Added secure push policy messaging to communication above (confirmation, request, and notification) - Added secure push policy messaging to package view for owners and admins - Fixed bug on security policy admin view where toggle all broken if multiple subscriptions - Updated security policy admin view to not reload page on update postback * Preserve original 409 exception in AI logs (#4136) * System.ArgumentNullException GET packages/DisplayPackage #4204 (#4210) * Resolve merge conflicts
Will email various scenarios w/ expected communication to the team shortly for review... since they differ slightly from @anangaur 's experience spec.