-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add attribute for media type validation (#1117)
- Loading branch information
Showing
8 changed files
with
202 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,17 @@ | ||
# Migrating from v7 to v8 | ||
|
||
## Major changes and new features | ||
## New features | ||
|
||
None | ||
- When using `FormFileGraphType` with type-first schemas, you may specify the allowed media | ||
types for the file by using the new `[MediaType]` attribute on the argument or input object field. | ||
|
||
## Breaking changes | ||
|
||
- The validation rules' signatures have changed slightly due to the underlying changes to the | ||
GraphQL.NET library. Please see the GraphQL.NET v8 migration document for more information. | ||
- The obsolete (v6 and prior) authorization validation rule has been removed. See the v7 migration | ||
GraphQL.NET library. Please see the GraphQL.NET v8 migration document for more information. | ||
- The obsolete (v6 and prior) authorization validation rule has been removed. See the v7 migration | ||
document for more information on how to migrate to the v7/v8 authorization validation rule. | ||
|
||
## Other changes | ||
|
||
- GraphiQL has been bumped from 1.5.1 to 3.2.0 | ||
- GraphiQL has been bumped from 1.5.1 to 3.2.0. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
using System.Collections; | ||
using MediaTypeHeaderValue = Microsoft.Net.Http.Headers.MediaTypeHeaderValue; | ||
|
||
namespace GraphQL.Server.Transports.AspNetCore; | ||
|
||
/// <summary> | ||
/// Ensures that the marked field argument or input object field is a valid media type | ||
/// via the <see cref="MediaTypeHeaderValue.IsSubsetOf(MediaTypeHeaderValue)"/> method | ||
/// and supports wildcards such as "<c>text/*</c>". | ||
/// </summary> | ||
/// <remarks> | ||
/// Only checks values of type <see cref="IFormFile"/>, or lists of <see cref="IFormFile"/>. | ||
/// Any other types of values will throw a run-time exception. | ||
/// </remarks> | ||
public class MediaTypeAttribute : GraphQLAttribute | ||
{ | ||
private readonly MediaTypeHeaderValue[] _mimeTypes; | ||
|
||
/// <inheritdoc cref="MediaTypeAttribute"/> | ||
public MediaTypeAttribute(params string[] mimeTypes) | ||
{ | ||
var types = MediaTypeHeaderValue.ParseList(mimeTypes); | ||
_mimeTypes = types as MediaTypeHeaderValue[] ?? types.ToArray(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Modify(FieldType fieldType, bool isInputType) | ||
{ | ||
var lists = fieldType.Type != null | ||
? CountNestedLists(fieldType.Type) | ||
: CountNestedLists(fieldType.ResolvedType | ||
?? throw new InvalidOperationException($"No graph type set on field '{fieldType.Name}'.")); | ||
fieldType.Validator += new Validator(lists, _mimeTypes).Validate; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Modify(QueryArgument queryArgument) | ||
{ | ||
var lists = queryArgument.Type != null | ||
? CountNestedLists(queryArgument.Type) | ||
: CountNestedLists(queryArgument.ResolvedType | ||
?? throw new InvalidOperationException($"No graph type set on field '{queryArgument.Name}'.")); | ||
queryArgument.Validator += new Validator(lists, _mimeTypes).Validate; | ||
} | ||
|
||
private class Validator | ||
{ | ||
private readonly int _lists; | ||
private readonly MediaTypeHeaderValue[] _mediaTypes; | ||
|
||
public Validator(int lists, MediaTypeHeaderValue[] mediaTypes) | ||
{ | ||
_lists = lists; | ||
_mediaTypes = mediaTypes; | ||
} | ||
|
||
public void Validate(object? obj) | ||
{ | ||
Validate(obj, _lists); | ||
} | ||
|
||
private void Validate(object? obj, int lists) | ||
{ | ||
if (obj == null) | ||
return; | ||
if (lists == 0) | ||
{ | ||
if (obj is IFormFile file) | ||
ValidateMediaType(file); | ||
else | ||
throw new InvalidOperationException("Expected an IFormFile object."); | ||
} | ||
else if (obj is IEnumerable enumerable && obj is not string) | ||
{ | ||
foreach (var item in enumerable) | ||
{ | ||
Validate(item, lists - 1); | ||
} | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException("Expected a list."); | ||
} | ||
} | ||
|
||
private void ValidateMediaType(IFormFile? file) | ||
{ | ||
if (file == null) | ||
return; | ||
var contentType = file.ContentType; | ||
if (contentType == null) | ||
return; | ||
var mediaType = MediaTypeHeaderValue.Parse(contentType); | ||
foreach (var validMediaType in _mediaTypes) | ||
{ | ||
if (mediaType.IsSubsetOf(validMediaType)) | ||
return; | ||
} | ||
throw new InvalidOperationException($"Invalid media type '{mediaType}'."); | ||
} | ||
} | ||
|
||
private static int CountNestedLists(Type type) | ||
{ | ||
if (!type.IsGenericType) | ||
return 0; | ||
|
||
var typeDef = type.GetGenericTypeDefinition(); | ||
|
||
if (typeDef == typeof(ListGraphType<>)) | ||
{ | ||
return 1 + CountNestedLists(type.GetGenericArguments()[0]); | ||
} | ||
|
||
if (typeDef == typeof(NonNullGraphType<>)) | ||
{ | ||
return CountNestedLists(type.GetGenericArguments()[0]); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
private static int CountNestedLists(IGraphType type) | ||
{ | ||
if (type is ListGraphType listGraphType) | ||
{ | ||
return 1 + CountNestedLists(listGraphType.ResolvedType ?? throw new InvalidOperationException($"ResolvedType not set for {listGraphType}.")); | ||
} | ||
|
||
if (type is NonNullGraphType nonNullGraphType) | ||
{ | ||
return CountNestedLists(nonNullGraphType.ResolvedType ?? throw new InvalidOperationException($"ResolvedType not set for {nonNullGraphType}.")); | ||
} | ||
|
||
return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters