33
44using System . Diagnostics . CodeAnalysis ;
55using System . Security . Claims ;
6- using HotChocolate . AspNetCore . Authorization ;
6+ using HotChocolate . Authorization ;
77using HotChocolate . Resolvers ;
88using Microsoft . AspNetCore . Http ;
99using Microsoft . Extensions . Primitives ;
@@ -15,7 +15,7 @@ namespace Azure.DataApiBuilder.Core.Authorization;
1515/// The changes in this custom handler enable fetching the ClientRoleHeader value defined within requests (value of X-MS-API-ROLE) HTTP Header.
1616/// Then, using that value to check the header value against the authenticated ClientPrincipal roles.
1717/// </summary>
18- public class GraphQLAuthorizationHandler : HotChocolate . AspNetCore . Authorization . IAuthorizationHandler
18+ public class GraphQLAuthorizationHandler : IAuthorizationHandler
1919{
2020 /// <summary>
2121 /// Authorize access to field based on contents of @authorize directive.
@@ -27,20 +27,24 @@ public class GraphQLAuthorizationHandler : HotChocolate.AspNetCore.Authorization
2727 /// </summary>
2828 /// <param name="context">The current middleware context.</param>
2929 /// <param name="directive">The authorization directive.</param>
30+ /// <param name="cancellationToken">The cancellation token - not used here.</param>
3031 /// <returns>
3132 /// Returns a value indicating if the current session is authorized to
3233 /// access the resolver data.
3334 /// </returns>
34- public ValueTask < AuthorizeResult > AuthorizeAsync ( IMiddlewareContext context , AuthorizeDirective directive )
35+ public ValueTask < AuthorizeResult > AuthorizeAsync (
36+ IMiddlewareContext context ,
37+ AuthorizeDirective directive ,
38+ CancellationToken cancellationToken = default )
3539 {
36- if ( ! IsUserAuthenticated ( context ) )
40+ if ( ! IsUserAuthenticated ( context . ContextData ) )
3741 {
3842 return new ValueTask < AuthorizeResult > ( AuthorizeResult . NotAuthenticated ) ;
3943 }
4044
4145 // Schemas defining authorization policies are not supported, even when roles are defined appropriately.
4246 // Requests will be short circuited and rejected (authorization forbidden).
43- if ( TryGetApiRoleHeader ( context , out string ? clientRole ) && IsInHeaderDesignatedRole ( clientRole , directive . Roles ) )
47+ if ( TryGetApiRoleHeader ( context . ContextData , out string ? clientRole ) && IsInHeaderDesignatedRole ( clientRole , directive . Roles ) )
4448 {
4549 if ( ! string . IsNullOrEmpty ( directive . Policy ) )
4650 {
@@ -53,19 +57,62 @@ public ValueTask<AuthorizeResult> AuthorizeAsync(IMiddlewareContext context, Aut
5357 return new ValueTask < AuthorizeResult > ( AuthorizeResult . NotAllowed ) ;
5458 }
5559
60+ /// <summary>
61+ /// Authorize access to field based on contents of @authorize directive.
62+ /// Validates that the requestor is authenticated, and that the
63+ /// clientRoleHeader is present.
64+ /// Role membership is checked
65+ /// and/or (authorize directive may define policy, roles, or both)
66+ /// an authorization policy is evaluated, if present.
67+ /// </summary>
68+ /// <param name="context">The authorization context.</param>
69+ /// <param name="directives">The list of authorize directives.</param>
70+ /// <param name="cancellationToken">The cancellation token.</param>
71+ /// <returns>The authorize result.</returns>
72+ public ValueTask < AuthorizeResult > AuthorizeAsync (
73+ AuthorizationContext context ,
74+ IReadOnlyList < AuthorizeDirective > directives ,
75+ CancellationToken cancellationToken = default )
76+ {
77+ if ( ! IsUserAuthenticated ( context . ContextData ) )
78+ {
79+ return new ValueTask < AuthorizeResult > ( AuthorizeResult . NotAuthenticated ) ;
80+ }
81+
82+ foreach ( AuthorizeDirective directive in directives )
83+ {
84+ // Schemas defining authorization policies are not supported, even when roles are defined appropriately.
85+ // Requests will be short circuited and rejected (authorization forbidden).
86+ if ( TryGetApiRoleHeader ( context . ContextData , out string ? clientRole ) && IsInHeaderDesignatedRole ( clientRole , directive . Roles ) )
87+ {
88+ if ( ! string . IsNullOrEmpty ( directive . Policy ) )
89+ {
90+ return new ValueTask < AuthorizeResult > ( AuthorizeResult . NotAllowed ) ;
91+ }
92+
93+ // directive is satisfied, continue to next directive.
94+ continue ;
95+ }
96+
97+ return new ValueTask < AuthorizeResult > ( AuthorizeResult . NotAllowed ) ;
98+ }
99+
100+ return new ValueTask < AuthorizeResult > ( AuthorizeResult . Allowed ) ;
101+ }
102+
56103 /// <summary>
57104 /// Get the value of the CLIENT_ROLE_HEADER HTTP Header from the HttpContext.
58105 /// HttpContext will be present in IMiddlewareContext.ContextData
59106 /// when HotChocolate is configured to use HttpRequestInterceptor
60107 /// </summary>
61- /// <param name="context ">HotChocolate Middleware Context</param>
108+ /// <param name="contextData ">HotChocolate Middleware Context data. </param>
62109 /// <param name="clientRole">Value of the client role header.</param>
63110 /// <seealso cref="https://chillicream.com/docs/hotchocolate/v12/server/interceptors#ihttprequestinterceptor"/>
64111 /// <returns>True, if clientRoleHeader is resolved and clientRole value
65112 /// False, if clientRoleHeader is not resolved, null clientRole value</returns>
66- private static bool TryGetApiRoleHeader ( IMiddlewareContext context , [ NotNullWhen ( true ) ] out string ? clientRole )
113+ private static bool TryGetApiRoleHeader ( IDictionary < string , object ? > contextData , [ NotNullWhen ( true ) ] out string ? clientRole )
67114 {
68- if ( context . ContextData . TryGetValue ( nameof ( HttpContext ) , out object ? value ) )
115+ if ( contextData . TryGetValue ( nameof ( HttpContext ) , out object ? value ) )
69116 {
70117 if ( value is not null )
71118 {
@@ -110,9 +157,9 @@ private static bool IsInHeaderDesignatedRole(string clientRoleHeader, IReadOnlyL
110157 /// Returns whether the ClaimsPrincipal in the HotChocolate IMiddlewareContext.ContextData is authenticated.
111158 /// To be authenticated, at least one ClaimsIdentity in ClaimsPrincipal.Identities must be authenticated.
112159 /// </summary>
113- private static bool IsUserAuthenticated ( IMiddlewareContext context )
160+ private static bool IsUserAuthenticated ( IDictionary < string , object ? > contextData )
114161 {
115- if ( context . ContextData . TryGetValue ( nameof ( ClaimsPrincipal ) , out object ? claimsPrincipalContextObject )
162+ if ( contextData . TryGetValue ( nameof ( ClaimsPrincipal ) , out object ? claimsPrincipalContextObject )
116163 && claimsPrincipalContextObject is ClaimsPrincipal principal
117164 && principal . Identities . Any ( claimsIdentity => claimsIdentity . IsAuthenticated ) )
118165 {
0 commit comments