Skip to content

JADNC: Required Input validation disabled for partial patching / relationships #781

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

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c4c27aa
disable validator if partial patch
sasman0001 Jun 9, 2020
ead6930
Create required validator that can be diabled to allow for partial pa…
sasman0001 Jun 9, 2020
a4c688d
formatting
sasman0001 Jun 9, 2020
7d3f57a
Remove unneeded reference.
sasman0001 Jun 9, 2020
11bd7d9
package reference
sasman0001 Jun 9, 2020
c02ef34
Requested changes:
sasman0001 Jun 10, 2020
9e40b88
Add version wildcard
sasman0001 Jun 10, 2020
a0de349
expect possible null in HttpContextExtension method
sasman0001 Jun 10, 2020
86989e6
change parameter order and naming for consistency
sasman0001 Jun 10, 2020
50ae2e4
Requested changes
sasman0001 Jun 10, 2020
102e636
Remove unneeded null check.
sasman0001 Jun 10, 2020
3917539
add null checks
sasman0001 Jun 10, 2020
cfbc9e3
generate fake name for author
sasman0001 Jun 10, 2020
0bcebe3
Moved logic to RequestDeserializer, overriding methods in BaseDocumen…
sasman0001 Jun 10, 2020
6a682ed
remove references
sasman0001 Jun 10, 2020
c2f2a69
formatting
sasman0001 Jun 10, 2020
81e764e
back to var
sasman0001 Jun 10, 2020
126a1a8
Requested changes:
sasman0001 Jun 11, 2020
a1596d5
formatting
sasman0001 Jun 11, 2020
d82f193
formatting
sasman0001 Jun 11, 2020
6882b19
change access modifiers
sasman0001 Jun 11, 2020
96cbd84
Requested changes:
sasman0001 Jun 15, 2020
945691d
refactor
sasman0001 Jun 15, 2020
4496d72
remoev comma.
sasman0001 Jun 15, 2020
4bc23b7
Merge remote-tracking branch 'upstream/master' into input_validation_…
sasman0001 Jun 16, 2020
7eeb6c5
Cleanup
Jun 16, 2020
44a9d92
removed unneeded code
Jun 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<AspNetCoreVersion>3.1.*</AspNetCoreVersion>
<EFCoreVersion>3.1.*</EFCoreVersion>
<NpgsqlPostgreSQLVersion>3.1.*</NpgsqlPostgreSQLVersion>
<DependencyInjectionVersion>3.1.*</DependencyInjectionVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
Expand All @@ -18,4 +19,4 @@
<BogusVersion>29.0.1</BogusVersion>
<MoqVersion>4.13.1</MoqVersion>
</PropertyGroup>
</Project>
</Project>
4 changes: 2 additions & 2 deletions benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Serialization.Server;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace Benchmarks.Serialization
Expand Down Expand Up @@ -38,8 +39,7 @@ public JsonApiDeserializerBenchmarks()
var options = new JsonApiOptions();
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
var targetedFields = new TargetedFields();

_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields);
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
}

[Benchmark]
Expand Down
2 changes: 2 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Models/Article.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.CustomValidators;

namespace JsonApiDotNetCoreExample.Models
{
public sealed class Article : Identifiable
{
[Attr]
[IsRequired(AllowEmptyStrings = true)]
public string Name { get; set; }

[HasOne]
Expand Down
2 changes: 2 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Models/Author.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using JsonApiDotNetCore.Models;
using System.Collections.Generic;
using JsonApiDotNetCore.Models.CustomValidators;

namespace JsonApiDotNetCoreExample.Models
{
public sealed class Author : Identifiable
{
[Attr]
[IsRequired(AllowEmptyStrings = true)]
public string Name { get; set; }

[HasMany]
Expand Down
1 change: 1 addition & 0 deletions src/Examples/ReportsExample/ReportsExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(DependencyInjectionVersion)" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(NpgsqlPostgreSQLVersion)" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions src/JsonApiDotNetCore/Extensions/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,20 @@ internal static void SetJsonApiRequest(this HttpContext httpContext)
{
httpContext.Items["IsJsonApiRequest"] = bool.TrueString;
}

internal static void DisableValidator(this HttpContext httpContext, string propertyName, string model = null)
{
if (httpContext == null) return;
var itemKey = $"JsonApiDotNetCore_DisableValidation_{model}_{propertyName}";
if (!httpContext.Items.ContainsKey(itemKey) && model != null)
{
httpContext.Items.Add(itemKey, true);
}
}
internal static bool IsValidatorDisabled(this HttpContext httpContext, string propertyName, string model)
{
return httpContext.Items.ContainsKey($"JsonApiDotNetCore_DisableValidation_{model}_{propertyName}") ||
httpContext.Items.ContainsKey($"JsonApiDotNetCore_DisableValidation_{model}_Relation");
}
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>4.0.0</VersionPrefix>
<TargetFramework>$(NetCoreAppVersion)</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.DataAnnotations;
using JsonApiDotNetCore.Extensions;

namespace JsonApiDotNetCore.Models.CustomValidators
{
public class IsRequiredAttribute : RequiredAttribute
{
private bool _isDisabled;

public override bool IsValid(object value)
{
return _isDisabled || base.IsValid(value);
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var httpContextAccessor = (IHttpContextAccessor)validationContext.GetRequiredService(typeof(IHttpContextAccessor));
_isDisabled = httpContextAccessor.HttpContext.IsValidatorDisabled(validationContext.MemberName, validationContext.ObjectType.Name);
return _isDisabled ? ValidationResult.Success : base.IsValid(value, validationContext);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using Microsoft.AspNetCore.Http;

namespace JsonApiDotNetCore.Serialization.Client
{
Expand All @@ -13,7 +14,7 @@ namespace JsonApiDotNetCore.Serialization.Client
/// </summary>
public class ResponseDeserializer : BaseDocumentParser, IResponseDeserializer
{
public ResponseDeserializer(IResourceContextProvider contextProvider, IResourceFactory resourceFactory) : base(contextProvider, resourceFactory) { }
public ResponseDeserializer(IResourceContextProvider contextProvider, IResourceFactory resourceFactory, IHttpContextAccessor httpContextAccessor) : base(contextProvider, resourceFactory, httpContextAccessor) { }

/// <inheritdoc/>
public DeserializedSingleResponse<TResource> DeserializeSingle<TResource>(string body) where TResource : class, IIdentifiable
Expand Down
41 changes: 32 additions & 9 deletions src/JsonApiDotNetCore/Serialization/Common/BaseDocumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.CustomValidators;
using JsonApiDotNetCore.Serialization.Client;
using JsonApiDotNetCore.Serialization.Server;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand All @@ -23,12 +25,15 @@ public abstract class BaseDocumentParser
{
protected readonly IResourceContextProvider _contextProvider;
protected readonly IResourceFactory _resourceFactory;
protected readonly IHttpContextAccessor _httpContextAccessor;

protected Document _document;

protected BaseDocumentParser(IResourceContextProvider contextProvider, IResourceFactory resourceFactory)
protected BaseDocumentParser(IResourceContextProvider contextProvider, IResourceFactory resourceFactory, IHttpContextAccessor httpContextAccessor)
{
_contextProvider = contextProvider;
_resourceFactory = resourceFactory;
_httpContextAccessor = httpContextAccessor;
}

/// <summary>
Expand Down Expand Up @@ -71,21 +76,38 @@ protected object Deserialize(string body)
/// <returns></returns>
protected IIdentifiable SetAttributes(IIdentifiable entity, Dictionary<string, object> attributeValues, List<AttrAttribute> attributes)
{
if (attributeValues == null || attributeValues.Count == 0)
return entity;

foreach (var attr in attributes)
{
if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue))
var disableValidator = false;
if (attributeValues == null || attributeValues.Count == 0)
{
disableValidator = true;
}
else
{
var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType);
attr.SetValue(entity, convertedValue);
AfterProcessField(entity, attr);
if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue))
{
var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType);
attr.SetValue(entity, convertedValue);
AfterProcessField(entity, attr);
}
else
{
disableValidator = true;
}
}

if (!disableValidator) continue;
if (_httpContextAccessor?.HttpContext?.Request.Method != "PATCH") continue;
if (attr.PropertyInfo.GetCustomAttribute<IsRequiredAttribute>() != null)
{
_httpContextAccessor.HttpContext.DisableValidator(attr.PropertyInfo.Name, attr.PropertyInfo.ReflectedType?.Name);
}
}

return entity;
}

/// <summary>
/// Sets the relationships on a parsed entity
/// </summary>
Expand All @@ -101,14 +123,15 @@ protected IIdentifiable SetRelationships(IIdentifiable entity, Dictionary<string
var entityProperties = entity.GetType().GetProperties();
foreach (var attr in relationshipAttributes)
{
_httpContextAccessor?.HttpContext?.DisableValidator( "Relation", attr.PropertyInfo.Name);

if (!relationshipsValues.TryGetValue(attr.PublicRelationshipName, out RelationshipEntry relationshipData) || !relationshipData.IsPopulated)
continue;

if (attr is HasOneAttribute hasOneAttribute)
SetHasOneRelationship(entity, entityProperties, hasOneAttribute, relationshipData);
else
SetHasManyRelationship(entity, (HasManyAttribute)attr, relationshipData);

}
return entity;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using JsonApiDotNetCore.Exceptions;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using Microsoft.AspNetCore.Http;

namespace JsonApiDotNetCore.Serialization.Server
{
Expand All @@ -13,8 +13,8 @@ public class RequestDeserializer : BaseDocumentParser, IJsonApiDeserializer
{
private readonly ITargetedFields _targetedFields;

public RequestDeserializer(IResourceContextProvider contextProvider, IResourceFactory resourceFactory, ITargetedFields targetedFields)
: base(contextProvider, resourceFactory)
public RequestDeserializer(IResourceContextProvider contextProvider, IResourceFactory resourceFactory, ITargetedFields targetedFields, IHttpContextAccessor httpContextAccessor)
: base(contextProvider, resourceFactory, httpContextAccessor)
{
_targetedFields = targetedFields;
}
Expand Down
11 changes: 9 additions & 2 deletions test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ namespace JsonApiDotNetCoreExampleTests.Acceptance
[Collection("WebHostCollection")]
public sealed class ManyToManyTests
{
private readonly Faker<Author> _authorFaker = new Faker<Author>()
.RuleFor(a => a.Name, f => f.Random.Words(2));

private readonly Faker<Article> _articleFaker = new Faker<Article>()
.RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10))
.RuleFor(a => a.Author, f => new Author());
.RuleFor(a => a.Author, f => new Author() { Name = "John Doe"});

private readonly Faker<Tag> _tagFaker;

Expand Down Expand Up @@ -282,7 +285,7 @@ public async Task Can_Create_Many_To_Many()
// Arrange
var context = _fixture.GetService<AppDbContext>();
var tag = _tagFaker.Generate();
var author = new Author();
var author = _authorFaker.Generate();
context.Tags.Add(tag);
context.AuthorDifferentDbContextName.Add(author);
await context.SaveChangesAsync();
Expand All @@ -294,6 +297,10 @@ public async Task Can_Create_Many_To_Many()
data = new
{
type = "articles",
attributes = new Dictionary<string, object>
{
{"name", "An article with relationships"}
},
relationships = new Dictionary<string, dynamic>
{
{ "author", new {
Expand Down
Loading