-
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
Temp keys user policies #3820
Temp keys user policies #3820
Conversation
@@ -293,6 +294,7 @@ private HttpStatusCodeWithBodyResult VerifyPackageKeyInternal(User user, Credent | |||
[RequireSsl] | |||
[ApiAuthorize] | |||
[ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)] | |||
[SecurityPolicy(SecurityPolicyAction.PackagePush)] |
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.
consider merging SecurityPolicy attribute with ApiAuthorize attribute.
builder.RegisterType<SecurityPolicyService>() | ||
.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.
Any reason for ISecurityPolicyService not to be a single instance (SingleInstance())?
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 should prefer InstancePerLifetimeScope over SuntingleInstance until we have a caching/perf/behavior requirement for it. It's easier for unintended interaction between requests to occur with SingleInstance
In reply to: 113238685 [](ancestors = 113238685)
/// </summary> | ||
public class SecurityPolicyService : ISecurityPolicyService | ||
{ | ||
public IEnumerable<UserSecurityPolicyHandler> UserPolicyHandlers { get; private set; } |
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.
consider making this a static
.WillCascadeOnDelete(true); | ||
|
||
modelBuilder.Entity<UserSecurityPolicy>() | ||
.HasKey(p => p.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.
Is not this duplication of the line 123.
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.
Removed
Looks great! 👍 |
private Version GetMaxOfMinClientVersions(UserSecurityPolicyContext context) | ||
{ | ||
var policyStates = context.Policies.Select(p => JsonConvert.DeserializeObject<State>(p.Value)); | ||
return policyStates.Max(s => s.MinClientVersion); |
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 be nulls among these values? In this case will the Max throw?
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.
Shouldn't be null, but added logic to guard against it
var clientVersionString = context.HttpContext.Request?.Headers[Constants.ClientVersionHeaderName]; | ||
|
||
Version clientVersion; | ||
return Version.TryParse(clientVersionString, out clientVersion) ? clientVersion : 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.
Do we want to log in AI the null case? The Evaluate below will return a non success result, the user scenario may be impacted in this 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.
Instead of logging in individual handlers, I'm planning to add telemetry and auditing to the SecurityPolicyService in a separate PR. AI can log any policy failures and include username, policy name and 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.
If that will be in a different PR is ok.
if (clientVersion == null || clientVersion < minClientVersion) | ||
{ | ||
return new SecurityPolicyResult(false, string.Format(CultureInfo.CurrentCulture, | ||
Strings.SecurityPolicy_RequireMinClientVersionForPush, minClientVersion)); |
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 want to log in AI the fact that the clientVersion < minClientVersion?
{ | ||
[JsonProperty("v")] | ||
[JsonConverter(typeof(VersionConverter))] | ||
public Version MinClientVersion { get; set; } |
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.
Version [](start = 19, length = 7)
should be a NuGetVersion, really
/// </summary> | ||
public abstract class UserSecurityPolicyHandler | ||
{ | ||
public string Name { get; private set; } |
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.
private [](start = 34, length = 7)
can remove private set;
This is readonly right?
/// <summary> | ||
/// Whether security policy criteria was successfully met. | ||
/// </summary> | ||
public bool Success { get; private set; } |
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.
private [](start = 35, length = 7)
can remove private set; as this is readonly
/// <summary> | ||
/// Type name for the policy handler that provides policy behavior. | ||
/// </summary> | ||
[Required] |
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.
Required [](start = 9, length = 8)
Can' this [Required] attribute be defined in EntitiesContext instead? Seems like you are requiring User.
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.
HasRequired in EntitiesContext is for a required relationship. I added Required here since it's the only non-nullable column that isn't a PK/FK. Not sure how this actually gets used, but we don't seem to be consistent with [Required] usage.
/// <summary> | ||
/// Security policy service. | ||
/// </summary> | ||
public ISecurityPolicyService SecurityPolicyService { get; private set; } |
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.
ISecurityPolicyService [](start = 15, length = 22)
why public?
[InlineData("4.1.0")] | ||
[InlineData("3.0.0")] | ||
[InlineData("2.0.0,4.1.0")] | ||
public void EvaluateReturnsFailureIfClientVersionLower(string minClientVersions) |
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.
EvaluateReturnsFailureIfClientVersionLower [](start = 20, length = 42)
client version is a NuGetVersion not a Version so we should test a couple NuGetVersion variants as well
- 4.1.0-beta1
🕐 |
/// Type name for the policy handler that provides policy behavior. | ||
/// </summary> | ||
[Required] | ||
public string Name { get; set; } |
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.
Name [](start = 22, length = 4)
I think type name is an implementation detail. We should have a hard coded constant for this (either an int which is consistent with the rest of the DB or const string).
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.
Policy handler names are now constants. I left as strings which I think will be easier to read when querying the DB. This PR only reads policies, but the next PR (onboarding) will start writing them.
public class RequirePackageVerifyScopePolicy : UserSecurityPolicyHandler | ||
{ | ||
public RequirePackageVerifyScopePolicy() | ||
: base(nameof(RequirePackageVerifyScopePolicy), SecurityPolicyAction.PackageVerify) |
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.
RequirePackageVerifyScopePolicy [](start = 26, length = 31)
Please use a const string or const int for this. nameof is easy to change with refactoring tools
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 you have unit tests for this so we aren't blind to the change, but I think we should be a bit more explicit about this string. It could even be a public const string property on this class.
In reply to: 113264288 [](ancestors = 113264288)
{ | ||
public static SecurityPolicyResult SuccessResult = new SecurityPolicyResult(true, null); | ||
|
||
public SecurityPolicyResult(bool success, string 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.
SecurityPolicyResult [](start = 15, length = 20)
seems like success = true and errorMessage != null is an invalid state, right? I like making the ctor private in this case and having a CreateErrorResult(string errorMessage) method.
yield return new RequireMinClientVersionForPushPolicy(); | ||
yield return new 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.
nit: remove extra line
public class RequirePackageVerifyScopePolicy : UserSecurityPolicyHandler | ||
{ | ||
public RequirePackageVerifyScopePolicy() | ||
: base(nameof(RequirePackageVerifyScopePolicy), SecurityPolicyAction.PackageVerify) |
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.
SecurityPolicyAction [](start = 60, length = 20)
Maybe I am missing it but where are we asserting that RequirePackageVerifyScopePolicy
has action PackageVerify
and not something else?
Functional test(s)? |
@joelverhagen I need to create onboarded test account for the functional test. Am thinking of doing onboarding in next PR, then auditing/telemetry, then functional test. |
Please make sure the deployment task mentions running EF migrations. |
// Resolve the policy service if security policy checks are required. | ||
if (SecurityPolicyAction.HasValue) | ||
{ | ||
SecurityPolicyService = ((AppController)filterContext.Controller)?.GetService<ISecurityPolicyService>(); |
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 will throw if the ApiAuthorizeAttribute
is used on a controller that doesn't extend AppController
, right? I would recommend that you add documentation that this attribute must be used on controllers that extend AppController
. Also, maybe throw an exception if the controller isn't an AppController
to make this easier to debug if this issue ever comes up?
*
var policyStates = context.Policies | ||
.Where(p => !string.IsNullOrEmpty(p.Value)) | ||
.Select(p => JsonConvert.DeserializeObject<State>(p.Value)); | ||
return policyStates.Max(s => s.MinClientVersion); |
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 particular reason for the policyStates
variable? You could merge the two statements into one and return its result directly.
/// <returns></returns> | ||
public static SecurityPolicyResult CreateErrorResult(string errorMessage) | ||
{ | ||
if (string.IsNullOrEmpty(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.
string.IsNullOrEmpty
seems a little too strong. Would a null check be better here?
Adding support for user security policies, including policies for use of temp keys and min client version
Onboarding and auditing will be separate PRs