|
| 1 | +using System.Reflection; |
1 | 2 | using System.Runtime.CompilerServices;
|
2 | 3 | using System.Runtime.InteropServices;
|
3 | 4 | using HotChocolate.Configuration;
|
|
10 | 11 | using HotChocolate.Utilities;
|
11 | 12 | using static HotChocolate.Authorization.AuthorizeDirectiveType.Names;
|
12 | 13 | using static HotChocolate.WellKnownContextData;
|
| 14 | +using static HotChocolate.Authorization.Properties.AuthCoreResources; |
13 | 15 |
|
14 | 16 | namespace HotChocolate.Authorization;
|
15 | 17 |
|
16 | 18 | internal sealed partial class AuthorizationTypeInterceptor : TypeInterceptor
|
17 | 19 | {
|
| 20 | + private const string AspNetCoreAuthorizeAttributeName = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; |
| 21 | + private const string AspNetCoreAllowAnonymousAttributeName = |
| 22 | + "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; |
| 23 | + |
| 24 | + private static readonly string _authorizeAttributeName = typeof(AuthorizeAttribute).FullName!; |
| 25 | + private static readonly string _allowAnonymousAttributeName = typeof(AllowAnonymousAttribute).FullName!; |
| 26 | + |
18 | 27 | private readonly List<ObjectTypeInfo> _objectTypes = [];
|
19 | 28 | private readonly List<UnionTypeInfo> _unionTypes = [];
|
20 | 29 | private readonly Dictionary<ObjectType, IDirectiveCollection> _directives = new();
|
@@ -114,14 +123,79 @@ public override void OnBeforeCompleteType(
|
114 | 123 | ITypeCompletionContext completionContext,
|
115 | 124 | DefinitionBase definition)
|
116 | 125 | {
|
| 126 | + if (definition is not ObjectTypeDefinition typeDef) |
| 127 | + { |
| 128 | + return; |
| 129 | + } |
| 130 | + |
117 | 131 | // last in the initialization we need to intercept the query type and ensure that
|
118 | 132 | // authorization configuration is applied to the special introspection and node fields.
|
119 |
| - if (ReferenceEquals(_queryContext, completionContext) && |
120 |
| - definition is ObjectTypeDefinition typeDef) |
| 133 | + if (ReferenceEquals(_queryContext, completionContext)) |
121 | 134 | {
|
122 | 135 | var state = _state ?? throw ThrowHelper.StateNotInitialized();
|
123 | 136 | HandleSpecialQueryFields(new ObjectTypeInfo(completionContext, typeDef), state);
|
124 | 137 | }
|
| 138 | + |
| 139 | + if (_context.Options.ErrorOnAspNetCoreAuthorizationAttributes && !completionContext.IsIntrospectionType) |
| 140 | + { |
| 141 | + var runtimeType = typeDef.RuntimeType; |
| 142 | + var attributesOnType = runtimeType.GetCustomAttributes().ToArray(); |
| 143 | + |
| 144 | + if (ContainsNamedAttribute(attributesOnType, AspNetCoreAuthorizeAttributeName)) |
| 145 | + { |
| 146 | + completionContext.ReportError( |
| 147 | + UnsupportedAspNetCoreAttributeError( |
| 148 | + AspNetCoreAuthorizeAttributeName, |
| 149 | + _authorizeAttributeName, |
| 150 | + runtimeType)); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + if (ContainsNamedAttribute(attributesOnType, AspNetCoreAllowAnonymousAttributeName)) |
| 155 | + { |
| 156 | + completionContext.ReportError( |
| 157 | + UnsupportedAspNetCoreAttributeError( |
| 158 | + AspNetCoreAllowAnonymousAttributeName, |
| 159 | + _allowAnonymousAttributeName, |
| 160 | + runtimeType)); |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + foreach (var field in typeDef.Fields) |
| 165 | + { |
| 166 | + if (field.IsIntrospectionField) |
| 167 | + { |
| 168 | + continue; |
| 169 | + } |
| 170 | + |
| 171 | + var fieldMember = field.ResolverMember ?? field.Member; |
| 172 | + |
| 173 | + if (fieldMember is not null) |
| 174 | + { |
| 175 | + var attributesOnResolver = fieldMember.GetCustomAttributes().ToArray(); |
| 176 | + |
| 177 | + if (ContainsNamedAttribute(attributesOnResolver, AspNetCoreAuthorizeAttributeName)) |
| 178 | + { |
| 179 | + completionContext.ReportError( |
| 180 | + UnsupportedAspNetCoreAttributeError( |
| 181 | + AspNetCoreAuthorizeAttributeName, |
| 182 | + _authorizeAttributeName, |
| 183 | + fieldMember)); |
| 184 | + return; |
| 185 | + } |
| 186 | + |
| 187 | + if (ContainsNamedAttribute(attributesOnResolver, AspNetCoreAllowAnonymousAttributeName)) |
| 188 | + { |
| 189 | + completionContext.ReportError( |
| 190 | + UnsupportedAspNetCoreAttributeError( |
| 191 | + AspNetCoreAllowAnonymousAttributeName, |
| 192 | + _allowAnonymousAttributeName, |
| 193 | + fieldMember)); |
| 194 | + return; |
| 195 | + } |
| 196 | + } |
| 197 | + } |
| 198 | + } |
125 | 199 | }
|
126 | 200 |
|
127 | 201 | public override void OnAfterCompleteTypes()
|
@@ -179,7 +253,7 @@ private void InspectObjectTypesForAuthDirective(State state)
|
179 | 253 |
|
180 | 254 | // if the field contains the AnonymousAllowed flag we will not
|
181 | 255 | // apply authorization on it.
|
182 |
| - if(fieldDef.GetContextData().ContainsKey(AllowAnonymous)) |
| 256 | + if (fieldDef.GetContextData().ContainsKey(AllowAnonymous)) |
183 | 257 | {
|
184 | 258 | continue;
|
185 | 259 | }
|
@@ -353,7 +427,7 @@ private void ApplyAuthMiddleware(
|
353 | 427 | {
|
354 | 428 | // if the field contains the AnonymousAllowed flag we will not apply authorization
|
355 | 429 | // on it.
|
356 |
| - if(fieldDef.GetContextData().ContainsKey(AllowAnonymous)) |
| 430 | + if (fieldDef.GetContextData().ContainsKey(AllowAnonymous)) |
357 | 431 | {
|
358 | 432 | return;
|
359 | 433 | }
|
@@ -621,6 +695,36 @@ private State CreateState()
|
621 | 695 |
|
622 | 696 | return new State(options ?? new());
|
623 | 697 | }
|
| 698 | + |
| 699 | + private static bool ContainsNamedAttribute(Attribute[] attributes, string nameOfAttribute) |
| 700 | + => attributes.Any(a => a.GetType().FullName == nameOfAttribute); |
| 701 | + |
| 702 | + private static ISchemaError UnsupportedAspNetCoreAttributeError( |
| 703 | + string aspNetCoreAttributeName, |
| 704 | + string properAttributeName, |
| 705 | + Type runtimeType) |
| 706 | + { |
| 707 | + return SchemaErrorBuilder.New() |
| 708 | + .SetMessage(string.Format(AuthorizationTypeInterceptor_UnsupportedAspNetCoreAttributeOnType, |
| 709 | + aspNetCoreAttributeName, runtimeType.FullName, properAttributeName)) |
| 710 | + .SetCode(ErrorCodes.Schema.UnsupportedAspNetCoreAuthorizationAttribute) |
| 711 | + .Build(); |
| 712 | + } |
| 713 | + |
| 714 | + private static ISchemaError UnsupportedAspNetCoreAttributeError( |
| 715 | + string aspNetCoreAttributeName, |
| 716 | + string properAttributeName, |
| 717 | + MemberInfo member) |
| 718 | + { |
| 719 | + var nameOfDeclaringType = member.DeclaringType?.FullName; |
| 720 | + var nameOfMember = member.Name; |
| 721 | + |
| 722 | + return SchemaErrorBuilder.New() |
| 723 | + .SetMessage(string.Format(AuthorizationTypeInterceptor_UnsupportedAspNetCoreAttributeOnMember, |
| 724 | + aspNetCoreAttributeName, nameOfDeclaringType, nameOfMember, properAttributeName)) |
| 725 | + .SetCode(ErrorCodes.Schema.UnsupportedAspNetCoreAuthorizationAttribute) |
| 726 | + .Build(); |
| 727 | + } |
624 | 728 | }
|
625 | 729 |
|
626 | 730 | static file class AuthorizationTypeInterceptorExtensions
|
|
0 commit comments