-
Notifications
You must be signed in to change notification settings - Fork 10.1k
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
Proposal for how we handle more metadata for auth #39892
Conversation
- The user can specify additional requirements or policies on the endpoint and they will be combined with existing IAuthorizeData.
/// <returns> | ||
/// A new <see cref="AuthorizationPolicy"/> which represents the combination of the | ||
/// authorization policies provided by the specified <paramref name="policyProvider"/>. | ||
/// </returns> | ||
public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) | ||
public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, |
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 care at all potential duplicate requirements in here? I guess currently we don't try to dedupe anything when we combine
This is also complicated enough that I wonder if this is worth exposing preemptively as some kind of IAuthorizationPolicyCombiner 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.
IAuthorizationPolicyCombiner
- No more abstractions 😄. I think the policy combining logic is actually pretty simple and I think we should de-dupe (actually if you have N authorize attributes you end up with the default policy added N times, but the system dedupes elsewhere).
Also side note: It's unfortunate we do all of this work per request. This could be done and cached on the endpoint once. |
var policies = endpoint?.Metadata.GetOrderedMetadata<AuthorizationPolicy>() ?? Array.Empty<AuthorizationPolicy>(); | ||
var reqirements = endpoint?.Metadata.GetOrderedMetadata<IAuthorizationRequirement>() ?? Array.Empty<IAuthorizationRequirement>(); | ||
|
||
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData, policies, reqirements); |
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.
Could we cache the policy it on Endpoint
as an internal property (assuming _policyProvider
is a default one)? This seems like to quite a bit to have to do on a per-request basis.
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.
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.
Unrelated to this PR but if you figure out a way to store objects per endpoint, this PR (#21675) could probably be revived too.
public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) | ||
public static Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, | ||
IEnumerable<IAuthorizeData> authorizeData) => CombineAsync(policyProvider, authorizeData, | ||
Enumerable.Empty<AuthorizationPolicy>(), |
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 wonder if it's better to use Array.Empty
here since it would benefit from a slightly faster Enumerable.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.
Only slightly in since Enumerable.Empty<T>
is also optimized for the Count check.
/// <param name="policyProvider">A <see cref="IAuthorizationPolicyProvider"/> which provides the policies to combine.</param> | ||
/// <param name="authorizeData">A collection of authorization data used to apply authorization to a resource.</param> | ||
/// <param name="policies">A collection of <see cref="AuthorizationPolicy"/> policies to combine.</param> | ||
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/>s to add to the auth policy.</param> |
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.
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/>s to add to the auth policy.</param> | |
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/> instances to add to the policy.</param> |
This looks nice so far. Does this make it any better in any situation now that you can specifying authentication schemes in the Policies this way as well? I guess now you can have pure AuthZ policies that don't specify any authentication schemes, and just put metadata on the endpoint that says whether it takes jwt or cookies, which maybe is a little better than before |
{ | ||
services.AddSingleton<IAuthorizationHandler, CustomAuthHandler>(); | ||
}, | ||
endpoint: CreateEndpoint(new AuthorizeAttribute(), new CustomRequirement("This"))); |
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 what happens here when you combine what's used to be a default policy situation of an empty authorize attribute and a custom requirement, it kind of looks like we wouldn't use the default policy, and would just use the custom requirement, which I think might be not what we want, since we'd lose the requirement of needing an authenticated user 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.
That's what I wanted to discuss. It seems like to don't use the default policy if one is already specified so I mimicked that approach here as well. Maybe that only applies when you have a custom policy but additional requirements don't affect that?
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.
Yeah I think everyone just assumes any kind of authorize brings along that RequireAuthenticatedUser that gets combined for free today. As long as we don't make them have to explicitly add it in any of these new usage scenarios it should be fine.
Right so I guess you can do:
|
So do we need to give them both the ability to define policies and requirements? Does it simplify anything if we only give them the ability to define additional policies basically we lose 3. and 4. Its only slightly more work to shove your requirements into an empty policy |
The problem is that policies aren't easily defined directly on the endpoint outside of minimal APIs. The only way to declaratively define a policy is the way we do today. I think 3 is a critical feature but it also worries me that you need both IAuthorizeData and the additional requirements. Feels like a recipe for security mistakes since you need both things. |
Thinking more about it, it would be possible to push this responsibility into the frameworks (creating a policy from requirements) but it would always override the default policy.... |
I mean we could always add a DefaultPolicyRequirement, which they could use to signal ask to get the combination. I think its simpler for the metadata to be limited to asking for more schemes and requirements, leave packaging/combining things to the frameworks, there's no notion of polices in the lowest level of |
@blowdart any thoughts about this before this starts getting real? |
I suggest talking to Jiachen who is doing user research into what users want, before assuming any suggested API changes help. |
The motivation behind this directly came out of some user feedback from the team building our docs site who had to do a lot of complex string parsing in their policy providers, which lead to focusing on making it easier to flow metadata from the app (authorize attribute), to the AuthZ handlers, see #39840 Users have been expressing what they want here for several years now, this PR is where we try to design something nicer as we always knew the PolicyProvider that only allowed the name of the policy as the communication was just a first step/workaround |
IAuthorizeData
.Contributes to #39840
This is what I was thinking the change could look like in all of the places that currently call into the auth system.
Some open questions:
aspnetcore/src/Http/Routing/src/EndpointMiddleware.cs
Line 38 in 3de52f5
cc @HaoK @pranavkm @BrennanConroy @Tratcher