|
| 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.UnsupportedAspNetCoreAttribute) |
| 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.UnsupportedAspNetCoreAttribute) |
| 726 | + .Build(); |
| 727 | + } |
624 | 728 | } |
625 | 729 |
|
626 | 730 | static file class AuthorizationTypeInterceptorExtensions |
|
0 commit comments