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

Support for policy propagation #4061

Merged
merged 16 commits into from
Jun 12, 2017
Merged

Support for policy propagation #4061

merged 16 commits into from
Jun 12, 2017

Conversation

chenriksson
Copy link
Member

  • Added propagation policy which can be manually subscribed from admin security policies view
  • Added 'add owner' confirmation from ManageUsers view, with policy warning if propagation detected
  • Modified add owner request email to include policy warning if propagation detected
  • Added email to existing owners once new owner is added, with policy warning if propagation detected
  • Removed unused queries from ManagePackageOwnersViewModel

Will email various scenarios w/ expected communication to the team shortly for review... since they differ slightly from @anangaur 's experience spec.

{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
Copy link
Contributor

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." }));
Copy link
Contributor

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

Copy link
Member Author

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();

Copy link
Contributor

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

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

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

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.

Copy link
Member Author

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

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

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.

{
foreach (var owner in package.Owners)
{
if (await SubscribeToSecurePushAsync(owner))
Copy link
Contributor

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.

Copy link
Contributor

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)

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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

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

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

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

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

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

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

@chenriksson
Copy link
Member Author

chenriksson commented Jun 9, 2017

Still looking into the following:

@chenriksson
Copy link
Member Author

Screenshot for DisplayPackage.cshtml (if owner or admin only):
displaypackagepolicymessage

Screenshot for ManagePackageOwners.cshtml confirmation (no policies):
manageownersaddconfirmationmessage

Screenshot for ManagePackageOwners.cshtml confirmation (with policies):
manageownersaddconfirmationmessagewithpolicies

Copy link
Contributor

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

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 ==.

Copy link
Member Author

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

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)

Copy link
Contributor

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

Choose a reason for hiding this comment

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

ArgumentNullException?

Copy link
Member Author

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.

Copy link
Contributor

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?

Copy link
Member Author

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

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?

@chenriksson chenriksson merged commit e4bd4cb into dev Jun 12, 2017
xavierdecoster added a commit that referenced this pull request Jun 13, 2017
- 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
joelverhagen added a commit that referenced this pull request Jun 19, 2017
* 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
@chenriksson chenriksson deleted the chenriks-propagate branch July 5, 2017 15:56
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