-
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
Auto-add Microsoft a co-owner for packages pushed by blessed Organizations #6150
Conversation
Correct issue link (Engineering): |
} | ||
} | ||
|
||
public void SendPackageAddedWithWarningsNotice(Package package, string packageUrl, string packageSupportUrl, string emailSettingsUrl, IEnumerable<string> warningMessages) |
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 copy, instead of adding optional warningMessages
parameter to existing SendPackageAddedNotice
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.
Ah, good one! Fixing :)
Url.AccountSettings(relativeUrl: false)); | ||
if (!packagePolicyResult.HasWarnings) | ||
{ | ||
// Notify user of push with warnings unless async validation in blocking mode is used |
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: comment says this is push with warnings, but that's the else
block
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.
Fixed.
} | ||
|
||
// This particular package policy assumes the existence of a 'Microsoft' user. | ||
// Succeed silentely (effectively ignoring this policy when enabled) when that user does not exist. |
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: silently
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.
Thx! Fixed.
if (context.ExistingPackageRegistration == null) | ||
{ | ||
// We are evaluating a newly pushed package with a new ID. | ||
var packageId = context.Package.PackageRegistration.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.
nit: packageRegistrationId
?
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.
Fixed.
if (!context.Package.PackageRegistration.IsVerified) | ||
{ | ||
// The owner has not reserved the prefix. Check whether the Microsoft user has. | ||
var prefixIsReservedByMicrosoft = IsPrefixReservedByAccount(context.EntitiesContext, microsoftUser, packageId); |
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 current owner doesn't own prefix, but MS does -- would the push have already failed before we got 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.
Yes... good catch!
The following error is shown when this happens today:
Response status code does not indicate success: 409 (This package ID has been reserved. Please request access to upload to this reserved namespace from the owner of the reserved prefix, or re-upload the package with a different ID.).
This happens in ApiController
on line 424, which happens before evaluating the package policies (as we only read the Stream and construct the package later in that call...).
Does that mean that, for users that have this RequireMicrosoftPackageCompliancePolicy
set, we should somehow skip that check?
According to the requirements set forth in NuGet/Engineering#1419, we generate a warning (to be sent in the publish successful email), and continue validation, which results in successful package push if the package is deemed compliant.
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.
@skofman1 @chenriksson any thoughts on this question?
Does that mean that, for users that have this
RequireMicrosoftPackageCompliancePolicy
set, we should somehow skip that check?
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 wouldn't skip the check--the more this Microsoft upload flow differs from the normal upload flow, the more likely we are to have bugs.
@skofman1 mentioned that she thinks this policy should be split into 2, one handling the reserved namespace ownership and the other verifying the metadata of the package. I think the reserved namespace policy could be evaluated after getting the ID and version (before the ownership checks are) and the metadata policy could trigger where the combined policy does.
This would enable you to check the reserved namespaces that the package applies to and add Microsoft as an owner of the package before the existing ownership checks are so you won't have to skip them.
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.
@scottbommarito in the event of a newly uploaded package id for which the prefix is not reserved by either the account pushing or Microsoft, we should still continue to validate metadata (see flowchart on NuGet/Engineering#1419), so there's an ordered dependency between such two policies (which is why I considered them one).
Furthermore, the current api scope validation would prevent the code to reach the metadata validation part.
In fact, the only thing that the prefix policy does according to the flowchart, is deciding whether an alternate email is to be sent upon successful validation of the metadata 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.
I think the flowchart is not important. The goal of the feature is that Microsoft is automatically added as a co-owner of packages owned by certain users, those packages must abide by certain metadata rules, and that an email must be sent when those packages are not in a reserved namespace. As long as those goals are accomplished, I don't think the ordering is important, especially given that the current ordering isn't working with our existing flow.
I think to resolve your issue, you should add Microsoft as a co-owner of the package (before the ownership check), make sure the ownership check includes Microsoft, and then do all the validations that you currently do where the evaluation currently is.
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.
Agree with Scott on this. I would keep the code as simple as possible to achieve the goals:
- Have a separate policy for metadata - if policy exists for account fail package push. In this case we don't need to worry about reserved namespaces at all.
- Have a separate policy for "auto add c-owner" - make sure to run this verification after the metadata one in the code (this might be as simple as putting it in the next block) if you see this on the account add co-owner and send mail if needed.
Regarding:
Does that mean that, for users that have this RequireMicrosoftPackageCompliancePolicy set, we should somehow skip that check?
Lets keep it simple and continue with the check. When a new MSFT team onboards we will manually assign to them all prefixes and security policies they need.
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.
So we can close up on this, and conclude that we do want an upload to fail if Microsoft owns the prefix but the pushing user does not.
This simplifies things a lot, and all validations can be part of a single package policy evaluated where it currently is (after api scope evaluation and before committing the package).
isWarning = !prefixIsReservedByMicrosoft; | ||
|
||
// Mark the package as verified if the prefix has been reserved by Microsoft. | ||
context.Package.PackageRegistration.IsVerified = prefixIsReservedByMicrosoft; |
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.
Do we need to monitor for these warnings, and possibly register these prefixes for these unverified packages to the team accounts we notice? I assume PMs would need to notify us, since they manage the MS account?
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.
As per the requirements set forth in NuGet/Engineering#1419, the owners will receive an email containing warning messages, and instructions to reserve the prefix.
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 make such emails also go to [email protected]? That's the alias we monitor for prefix reservation.
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, but it's not the policy who is in charge of sending the emails. It's the ApiController
that sends them.
Do we want to include [email protected]
in all package added notices that generated warnings during package policy validation? (currently, this is the only package 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.
only in case of prefix not reserved warning, we should send to [email protected]. If this is easily doable, great! If not, that's ok too, Daniel and I can just create a new filter based on the subject line :)
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 non-trivial to make that distinction currently, so I guess the filter is the way to go for now :)
var reservedNamespacesForId = (from request in entitiesContext.Set<ReservedNamespace>() | ||
where (request.IsPrefix && id.StartsWith(request.Value)) | ||
|| (!request.IsPrefix && id.Equals(request.Value)) | ||
select request).ToList(); |
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.
Should this logic be in the ReservedNamespaceService
, like the other checks?
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 tricky part is that I don't have access to the ReservedNamespaceService
and I can't inject it without causing a circular dependency loop (which is why the IEntitiesContext
is passed along instead, which is the most generic way of providing access to the db).
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 you explain the circular dependency loop? I don't see it here, and I think it would be easy for ApiController
to pass a ReservedNamespaceService
to the context.
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 did exactly that before changing it again to the current approach, and had a circular dependency error when injecting both ReservedNamespaceService
and UserService
instead of IEntitiesContext
, as ReservedNamespaceService
also depends on UserService
.
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.
So, to clarify
UserService
depends onSecurityPolicyService
ReservedNamespaceService
depends onUserService
SecurityPolicyService
creates thePackageSecurityPolicyEvaluationContext
to pass to theRequireMicrosoftPackageCompliancePolicy
using the services it's been injected with
Regardless of this loop, I think it's really important for you to be able to get these services for your DI. After discussing this a bit with @loic-sharma, he suggested making SecurityPolicyService
dependent on the DI container (IContainer
?) and then using it to get the services. There might be a better way, but I think it's important you can get access to whatever services you need in the policy handler.
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 it is important to gain access to those services...
Do we have an example where we inject the Autofac.IContainer
anywhere? Or should we use DependencyResolver.Current
?
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.
To clarify @scottbommarito's comment: I suggested that you use the service locator pattern. From this StackOverflow post, it seems that the proper pattern in Autofac is to accept an IComponentContext
.
I don't know off-hand if the Gallery has used this pattern before. However, I do know that the Service Bus jobs use the service locator pattern using ASP.NET's dependency injection libraries 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.
Circular dependencies are typically resolved by requesting Lazy<X>
(or Func<X>
) instead of X
in one of the constructors to break the loop. Is it possible to do in this case?
private bool IsPackageMetadataCompliant(Package package) | ||
{ | ||
// Author validation | ||
if (!package.FlattenedAuthors.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Contains(MicrosoftUsername)) |
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.
Contains
should probably use IEqualityComparer
w/o ignore case?
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 usernames case-sensitive? I thought they weren't, so we should ignore casing?
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.
They're not case sensitive, but I believe code analysis usually suggests that equality comparer be used in these APIs. It may be good to be explicit anyways - using InvariantCulture, instead of InvariantCultureIgnoreCase.
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 believe you meant InvariantCultureIgnoreCase
to be used, as the comparison is not case-sensitive. Fixed.
// If the package being evaluated has not been registered yet (new package ID), | ||
// this policy requires the prefix to be reserved. | ||
var isWarning = false; | ||
if (context.ExistingPackageRegistration == 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.
Can we split this policy into two policies:
- Policy that ensure reserved prefix
- Policy that validates metadata. The metadata policy will be general, i.e. the required values will be loaded from the DB config. This will also be useful to configure it differently for teams that use different copyrights, like Xamarin.
The two policies can be part of a "Subscription", like what we did for Secure Push.
if (!string.Equals(package.Copyright, "(c) Microsoft Corporation. All rights reserved.") | ||
&& !string.Equals(package.Copyright, "© Microsoft Corporation. All rights reserved.")) | ||
{ | ||
return false; |
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 give the user a good error message about why the package is not compliant.
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.
Updated.
/// <param name="package">The package to evaluate.</param> | ||
/// <param name="packageRegistration">The package registration. Will be <code>null</code> if the <paramref name="package"/> has a new package ID.</param> | ||
/// <returns></returns> | ||
public Task<SecurityPolicyResult> EvaluatePackagePoliciesAsync( |
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 name implies that the security policy is on a package, and not on a user. But it's actually on a user... perhaps we should use the existing method name (EvaluateUserPoliciesAsync)? Same thought about other parts of the code. What do you think?
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.
They evaluate a package, not a user. The user is the subscriber to the policy, but the object being evaluated by the policy is the package.
The API is also quite different, as a Package
, PackageRegistration
and IEntitiesContext
are provided to the handlers to do their thing. They are also evaluated at a different time in the flow. To me, they are similar, yet different.
{ | ||
public const string PolicyName = nameof(RequireMicrosoftPackageCompliancePolicy); | ||
|
||
internal const string MicrosoftUsername = "Microsoft"; |
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 Microsoft be a parameter to the policy (from DB)?
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'm assuming you refer to the JSON State
stored with the policy? Sure thing.
There's likely more to it than just changing this username to make this a reusable policy (e.g. copyright notice, maybe additional or different validations).
I thought we discussed that for v1, this was not critical.
To be able to use this in a flexible way, we'll need to work on admin UI, right?
In fact, as this policy is not part of the DefaultSubscription
, we need an explicit action to subscribe to this policy given a set of configurable parameters (similar to the "Transform user to organization" 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.
I think we have two options here:
- Keep the policies general and configurable, and add the "Microsoft" specific settings in the Subscription level. i.e, there will be a MicrosoftTeam subscription that will create policies with the specific configs for user name, copyright, etc. The admin UI can remain simple. In edge cases like Xamarin packages needing different config, we can go to DB.
- Keep the subscription general as well, and have the user provide the config in the admin UI. It's more UI work but helps avoid harding "Microsoft" in our code.
isWarning = !prefixIsReservedByMicrosoft; | ||
|
||
// Mark the package as verified if the prefix has been reserved by Microsoft. | ||
context.Package.PackageRegistration.IsVerified = prefixIsReservedByMicrosoft; |
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 feels hacky. By setting the flag here directly we are bypassing the normal code path that's used to set this flag. Furthermore, this could lead to many issues because most of the code does not expect a package to be marked as verified when it doesn't actually have any owners that own its reserved namespace.
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, I haven't yet reviewed the full code, but, we shouldn't mark the package as verified during upload, rather we should allow the upload(the restriction on the non-owner of namespace) and when Microsoft is added, it should auto add the IsVerified since it owns the namespace it belongs to.
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're no longer setting IsVerified
manually. This is now done by calling the IPackageOwnershipManagementService.AddPackageOwnerAsync
API.
// Automatically add 'Microsoft' as co-owner when metadata is compliant. | ||
if (!context.Package.PackageRegistration.Owners.Select(o => o.Username).Contains(MicrosoftUsername, StringComparer.OrdinalIgnoreCase)) | ||
{ | ||
context.Package.PackageRegistration.Owners.Add(microsoftUser); |
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.
PackageService.AddPackageOwnerAsync
is the intended way to add owners to a PackageRegistration
. We are bypassing the normal code path here which WILL result in the required signer of the package not being set as expected. I know there is also a method that will not only add the owner but also mark the package as verified if the owner owns the reserved namespace it's in. I would suggest doing that instead of making these changes to the package manually.
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 for the pointers! Those calls all commit to the db, right?
This "manual" stuff may happen before the registration even exists in the db... (e.g. when pushing a new ID). Committing to the db should remain a single atomic operation handled by the IUploadService
. I may need to tweak those existing APIs with a bool commitChanges = true
argument.
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.
Adding such overload for SetRequiredSignerAsync
is maybe not what we're after...
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.
Yep, we regularly add bool commitChanges = true
to our existing APIs if we need to call them but not commit. I would do that and then call the API here.
After looking through the code, it seems like PackageOwnershipManagementService.AddPackageOwnerAsync
is the API you should be calling. I would add the commitChanges
parameter there and to any APIs that it calls.
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.
Adding the commitChanges
parameter to IPackageOwnershipManagementService.AddPackageOwnerAsync
(which creates a db transaction) affects many other APIs called by this method.
Including the PackageService.SetRequiredSignerAsync
method. How would you add this parameter in there, which also saves audit records?
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 all makes me wonder if it would make sense for these new package policies to have a Pre-Upload and Post-Upload evaluation moment...
Check prefix ownership and metadata validation pre-upload, and if the policy generates no errors, commit the package (and registration in case of new id), then continue with post-upload actions as defined by the policy and based on pre-upload evaluation result...
However, the co-owner must be added before signing validation.
Thoughts? Could this potentially mess up async validation? (what triggers the async validation?)
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.
Any additional commits in the upload flow WILL break async validation. We had a live site that was caused by a similar bug. You may be able to get away with additional commits if they happen AFTER the existing commit but it sounds risky, especially if the first commit succeeds and the second fails.
Regarding adding commitChanges
, I would check around, but I think in most places we just change
await _entitiesContext.SaveChangesAsync();
to
if (commitChanges)
{
await _entitiesContext.SaveChangesAsync();
}
while ignoring anything else going on in the method, including audit records. I think we already accept that it's possible that an audit record may be emitted for an operation that failed afterwards.
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.
Handled.
var reservedNamespacesForId = (from request in entitiesContext.Set<ReservedNamespace>() | ||
where (request.IsPrefix && id.StartsWith(request.Value)) | ||
|| (!request.IsPrefix && id.Equals(request.Value)) | ||
select request).ToList(); |
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 you explain the circular dependency loop? I don't see it here, and I think it would be easy for ApiController
to pass a ReservedNamespaceService
to the context.
src/NuGetGallery/Strings.resx
Outdated
@@ -911,4 +911,10 @@ If you would like to update the linked Microsoft account you can do so from the | |||
<data name="FailedValidationHardDeleteReason" xml:space="preserve"> | |||
<value>Automatic hard-delete for reupload of package that failed validation</value> | |||
</data> | |||
<data name="SecurityPolicy_RequireMicrosoftPackageMetadataComplianceForPush" xml:space="preserve"> | |||
<value>Package is not compliant with metadata requirements for Microsoft packages on NuGet.org. Go to <insert URL here> for more information.</value> |
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.
reminder that insert URL
needs to be updated when the URL is available
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 package is not compliant with metadata requirements for Microsoft packages on NuGet.org. Go to http://aka.ms/Microsoft-NuGet-Compliance for more information.
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! :) Fixed.
private bool IsPackageMetadataCompliant(Package package) | ||
{ | ||
// Author validation | ||
if (!package.FlattenedAuthors.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Contains(MicrosoftUsername)) |
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 there anything else that parses the list of FlattenedAuthors
? I feel like there should be a shared API for parsing the FlattenedAuthors
into a string list.
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 seems to be the only place in the gallery code where we need to do this.
|
||
public Task OnSubscribeAsync(UserSecurityPolicySubscriptionContext context) | ||
{ | ||
return Task.CompletedTask; |
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 we should enumerate through the user's packages and add Microsoft
as a package owner if the package passes the metadata requirements when a user is onboarded to this 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.
That sounds like a good idea. We should also unlock the package if it is locked as part of adding Microsoft a co-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.
Created tracking issue for this suggestion.
@@ -42,5 +62,9 @@ public static SecurityPolicyResult CreateErrorResult(string errorMessage) | |||
/// Error message, if the security policy criteria was not met. | |||
/// </summary> | |||
public string ErrorMessage { get; } | |||
|
|||
public bool HasWarnings { get { return _warningMessages.Any(); } } |
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: public bool HasWarnings => _warningMessages.Any();
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.
Fixed.
|
||
public bool HasWarnings { get { return _warningMessages.Any(); } } | ||
|
||
public IReadOnlyCollection<string> WarningMessages { get { return _warningMessages; } } |
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: public IReadOnlyCollection<string> WarningMessages => _warningMessages;
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.
Fixed.
17be044
to
f5da0de
Compare
src/NuGetGallery/Strings.resx
Outdated
<value>The package is not compliant with metadata requirements for Microsoft packages on NuGet.org. Go to http://aka.ms/Microsoft-NuGet-Compliance for more information.</value> | ||
</data> | ||
<data name="SecurityPolicy_RequirePackagePrefixReserved" xml:space="preserve"> | ||
<value>You have not published a package with this prefix in the past. This means other users may be able to push packages starting with the same prefix. Contact [email protected] to reserve the prefix. Learn more about Package ID prefix reservation.</value> |
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.
You have not published a package with this prefix in the past. This means other users may be able to push packages starting with the same prefix. Contact [email protected] to reserve the prefix. Learn more about Package ID prefix reservation. [](start = 11, length = 242)
The "learn more" bit is missing a link. What do you think of this wording?
This is the first time you've published a package with this prefix. Other users may be able to push packages with similar IDs. You can contact [email protected] to reserve the prefix. Go to for more information.
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.
@karann-msft what is the link to be used here? (to learn more about prefix reservation)
This one? https://docs.microsoft.com/en-us/nuget/reference/id-prefix-reservation
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.
@xavierdecoster yes that looks right. sorry for the late reply.
var warningMessagesPlaceholder = string.Empty; | ||
if (hasWarnings) | ||
{ | ||
subject = $"[{CoreConfiguration.GalleryOwner.DisplayName}] Package published with Warnings - {package.PackageRegistration.Id} {package.Version}"; |
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.
Warnings [](start = 98, length = 8)
Should this be lower cased?
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.
Fixed.
d480107
to
6228911
Compare
on nuget.org async validation is enabled, so this code block will never be called (the notification mail is sent by the orchestrator after validation is done) Refers to: src/NuGetGallery/Controllers/ApiController.cs:533 in 6228911. [](commit_id = 6228911d9196b59402fffc2c12f6350378d1eb02, deletion_comment = False) |
} | ||
|
||
// Copyright validation | ||
if (!string.Equals(package.Copyright, "(c) Microsoft Corporation. All rights reserved.") |
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.
hardcoding the copyright allowed values will surely get us in trouble in the future when we deal with Xamarin packages for example. We should at least have the option to go to the DB and manually modify the requirement for those packages.
{ | ||
// This will also mark the package as verified if the prefix has been reserved by Microsoft. | ||
// The entities context is committed later as a single atomic transaction (see PackageUploadService). | ||
await context.PackageOwnershipManagementService.AddPackageOwnerAsync(context.Package.PackageRegistration, microsoftUser, commitChanges: false); |
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 about this order of validations:
- Check metadata - exit with failure if not compliant.
- Add MSFT as co-owner - this will set the "isVerified" if needed.
- Check if "IsVerified" is true - return warning if not.
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.
Sounds good. In that case, I don't even need the ReservedNamespaceService
in this policy, as I can simply check on context.Package.PackageRegistration.IsVerified
at the end of the policy evaluation.
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.
Updated order of validations.
@@ -27,10 +28,24 @@ public CoreMessageService(IMailSender mailSender, ICoreMessageServiceConfigurati | |||
public IMailSender MailSender { get; protected set; } | |||
public ICoreMessageServiceConfiguration CoreConfiguration { get; protected set; } | |||
|
|||
public void SendPackageAddedNotice(Package package, string packageUrl, string packageSupportUrl, string emailSettingsUrl) | |||
public void SendPackageAddedNotice(Package package, string packageUrl, string packageSupportUrl, string emailSettingsUrl, IEnumerable<string> warningMessages = 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.
This mail will never be sent by the Gallery on nuget.org (but by the orchestrator). You should create a separate mail for the warning.
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.
Great point! Plumbing the warnings down to orchestrator would be a mess.
Couldn't we do the warning emails asynchronously for "blessed" owners? This would also mean we could check for existing IDs that don't fall under a namespace.
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.
Where do you suggest I add this warning email in code?
I could add the following in ApiController
after the if
block that checks for blocking validation mode:
// Emit warning messages if any
else if (packagePolicyResult.HasWarnings)
{
// Notify user of push unless async validation in blocking mode is used
MessageService.SendPackageAddedNotice(package,
Url.Package(package.PackageRegistration.Id, package.NormalizedVersion, relativeUrl: false),
Url.ReportPackage(package.PackageRegistration.Id, package.NormalizedVersion, relativeUrl: false),
Url.AccountSettings(relativeUrl: false),
packagePolicyResult.WarningMessages);
}
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 @joelverhagen 's idea to send warning e-mails about packages that don't fall under a namespace from a separate job. However, it will be much more work, so I vote to keep it in the gallery.
One thing I would do, is have a separate e-mail with this warning and not the "package added notice" because people will get the same e-mail twice - one from Gallery and one from Orchestrator and will find it confusing.
.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) | ||
.Contains(MicrosoftUsername, StringComparer.InvariantCultureIgnoreCase)) | ||
{ | ||
return false; |
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.
false [](start = 23, length = 5)
How does the caller know what the specific problem is?
Shouldn't we be returning an error message, e.g. "The package copyright does not match the policy blah blah".
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.
Updated.
@@ -489,6 +490,17 @@ public virtual Task<ActionResult> CreatePackagePost() | |||
owner, | |||
currentUser); | |||
|
|||
var packagePolicyResult = await SecurityPolicyService.EvaluatePackagePoliciesAsync( |
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.
EvaluatePackagePoliciesAsync [](start = 82, length = 28)
The implication here is that the "Package" entity has enough details for package policies to be performed. You'll notice that pretty much all of the other package validation above acts on PackageMetadata, PackageArchiveReader, or PackageRegistration (if it exists).
Given PackageMetadata and PackageArchiveReader is a superset of the data available on Package, why not use these in the validation instead?
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.
Because when generating the Package
, the IsVerified
property is properly set already, which gives the policy an indication of whether the account pushing the package has registered the prefix yet.
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 can move it up, so this is called before creating the Package
and BEFORE adding the entity to the entity context, but that would likely mean we make the call to check for IsVerified for the pushing user twice (once when evaluating the policy, once when creating 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.
I see, there is a sneaky dependency on IsVerified
. Makes sense.
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.
An additional dependency: when the package being pushed concerns a new package registration, the PackageRegistration
will be null
if we didn't create the Package
entity yet, so there's no way we can add an owner to the registration in that case.
I think there's no other way but to run this policy after generating the Package
entity and adding it to the db context.
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 updated PackageSecurityPolicyEvaluationContext
to no longer depend on IEntitiesContext
directly, but instead leverage IUserService
(and IPackageOwnershipManagementService
).
I added the following test that asserts we call their APIs with commitChanges: false
.
https://github.com/NuGet/NuGetGallery/pull/6150/files#diff-3e9af76f191871062da1ac6b81a382ecR48
|
||
if (!packagePolicyResult.Success) | ||
{ | ||
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, packagePolicyResult.ErrorMessage); |
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.
HttpStatusCodeWithBodyResult [](start = 43, length = 28)
We have to now performed validations AFTER adding the entity to the entity context.
This is only okay if we are very rigorous about not committing (SaveChangesAsync on entity context) until we are very sure all validations are done.
There was a time in the past when uploading a readme Markdown during UI upload actually did a commit early. This was a bug that went undetected for months.
Would it be possible to run this validation before adding entities to the entity context?
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 am okay with leaving this after mutating the DB context, but we need a safe guard to be sure that commit does not be called early. Any ideas on that? It seems like we need concrete implementations of all ApiController dependencies in a unit test EXCEPT DB context and then we say "Moq, make sure SaveChangesAsync was only called once".
In reply to: 205230405 [](ancestors = 205230405)
// If the package being evaluated has not been registered yet (new package ID), | ||
// this policy requires the prefix to be reserved. | ||
var isWarning = false; | ||
if (context.ExistingPackageRegistration == 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.
ExistingPackageRegistration [](start = 24, length = 27)
Why isn't this a boolean, PackageRegistrationAlreadyExists
? We don't actually need the existing instance since it is on Package.PackageRegistration, 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.
True! Fixing.
d5fb57c
to
6bfb7e9
Compare
return SecurityPolicyResult.CreateErrorResult( | ||
string.Format( | ||
CultureInfo.CurrentCulture, | ||
Strings.SecurityPolicy_RequireMicrosoftPackageMetadataComplianceForPush, |
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.
you removed "Microsoft" from this code, but it's still here. I guess there's no easy way to make the string generic given the URL is hardcoded.
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.
Oversight :) The error message could be part of the policy state.
@skofman1 oops, good catch :) I've pushed an update that addresses that scenario. Notice the subtle difference in email subject ( Merging is blocked by @scottbommarito's request for changes, but if all good, feel free to merge and deploy to DEV later today. |
…ComplianceForPush
…s used in SecurityPolicyService. Calling IPackageOwnershipManagementService in RequireMicrosoftPackageCompliancePolicy (instead of using IEntitiesContext directly)
…mit changes in the policy
…ation is disabled
361f3dd
to
53df528
Compare
@scottbommarito your review is still requesting changes. Can you please check if changes are good, or let me know what needs changed still? @karann-msft @skofman1 had an offline discussion with @joelverhagen regarding the email with warnings message, and he had an interesting suggestion:
I guess it all depends on the answer to the above question, and the amount of effort we want to put into it. |
Xavier, as I commented above regrading Joel's suggestion: it's good, but let's not invest the work now, and leave the code as is. |
All CR feedback addressed. Created tracking issue for OnSubscribeAsync suggestion.
… Organizations (#6150)"
* Revert "Added package compliance policy checks when uploading through NuGet.org UI (#6272)" This reverts commit af20787. * Revert "Support MicrosoftTeamSubscription on organization accounts (#6264)" This reverts commit 945e2fa. * Revert "Auto-add Microsoft as co-owner for packages pushed by blessed Organizations (#6150)"
Fixes https://github.com/NuGet/Engineering/issues/1419
Opening this PR already for early review and feedback.
Note: this focuses on the package push API side of things. Not covering the web upload scenario, yet.