Skip to content
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

Disallow Encode(payload) with AddClaim(s) #464

Merged
merged 27 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4241ac5
Fix decoding/encoding payloads with attribute decoration #456
hartmark Jan 24, 2023
1cf67c3
Update JwtBuilder.cs
abatishchev Jan 25, 2023
fc7807b
Update JwtBuilder.cs
abatishchev Jan 25, 2023
7c17332
Update JWT.csproj
abatishchev Jan 25, 2023
23f2c6a
order
abatishchev Jan 25, 2023
74a9810
order, switch/case
abatishchev Jan 25, 2023
223ac2d
|| NET7_0
abatishchev Jan 25, 2023
e519d8f
City
abatishchev Jan 25, 2023
4fcbc15
tests
abatishchev Jan 25, 2023
c8a78e9
prop
abatishchev Jan 25, 2023
03cc940
Fallback to property-name if serializer is unknown
Jan 25, 2023
827fcf7
Added more tests for complex claims
Jan 25, 2023
3a5945a
Update JwtBuilderDecodeTests.cs
abatishchev Jan 25, 2023
ce524cf
Update TestData.cs
abatishchev Jan 25, 2023
0ae353c
WIP JwtEncoder.Encode() now accepts a list of payloads so we can use …
hartmark Jan 25, 2023
73948aa
Revert "WIP JwtEncoder.Encode() now accepts a list of payloads so we …
hartmark Jan 27, 2023
8b9ac59
UnitTest count for Net60 and Net70 is now the same
hartmark Jan 27, 2023
5c2787b
Disallow Encode(payload) with AddClaim(s)
hartmark Jan 31, 2023
ff860a5
Update JwtBuilder.cs
abatishchev Jan 31, 2023
979e159
Update JwtBuilderEncodeTests.cs
abatishchev Jan 31, 2023
e48515b
Update JwtBuilderEncodeTests.cs
abatishchev Jan 31, 2023
1f3e8d9
Update JwtBuilderDecodeTests.cs
abatishchev Jan 31, 2023
ab55426
Update JwtBuilderDecodeTests.cs
abatishchev Jan 31, 2023
4d6c349
Update JWT.csproj
abatishchev Jan 31, 2023
ba6f381
Update JWT.Extensions.AspNetCore.csproj
abatishchev Jan 31, 2023
32b08b9
Update JWT.Extensions.DependencyInjection.csproj
abatishchev Jan 31, 2023
7920a17
Update CHANGELOG.md
abatishchev Jan 31, 2023
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
71 changes: 57 additions & 14 deletions src/JWT/Builder/JwtBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System;
using System;
using System.Linq;
using System.Reflection;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;

#if MODERN_DOTNET
using System.Text.Json.Serialization;
#endif

using static JWT.Internal.EncodingHelper;

namespace JWT.Builder
Expand Down Expand Up @@ -263,27 +269,19 @@ public string Encode()
return _encoder.Encode(_jwt.Header, _jwt.Payload, _secrets?[0]);
}

public string Encode<T>(T payload) =>
Encode(typeof(T), payload);

public string Encode(Type payloadType, object payload)
public string Encode(object payload)
{
if (payloadType is null)
throw new ArgumentNullException(nameof(payloadType));
if (payload is null)
throw new ArgumentNullException(nameof(payload));

EnsureCanEncode();

var dic = payloadType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(payload, null));
EnsureCanEncode();

foreach (var pair in dic)
if (_jwt.Payload.Any())
{
_jwt.Payload.Add(pair.Key, pair.Value);
throw new NotSupportedException("Supplying both key-value pairs and implicit payload is not supported.");
}

return Encode();
return _encoder.Encode(_jwt.Header, payload, _secrets?[0]);
}

/// <summary>
Expand Down Expand Up @@ -320,6 +318,14 @@ public T DecodeHeader<T>(string token)
return _decoder.DecodeHeader<T>(token);
}

public object Decode(string token, Type type)
{
EnsureCanDecode();

return _decoder.DecodeToObject(type, token, _secrets, _valParams.ValidateSignature);
}


/// <summary>
/// Decodes a token using the supplied dependencies.
/// </summary>
Expand Down Expand Up @@ -468,5 +474,42 @@ private bool CanDecodeHeader()

return true;
}

private string GetPropName(MemberInfo prop)
{
var jsonSerializer = _jsonSerializerFactory.Create();

var customAttributes = prop.GetCustomAttributes(inherit: true);
foreach (var attribute in customAttributes)
{
switch (jsonSerializer)
{
case JsonNetSerializer:
{
if (attribute is JsonPropertyAttribute jsonNetProperty)
{
return jsonNetProperty.PropertyName;
}
break;
}
#if MODERN_DOTNET
case SystemTextSerializer:
{
if (attribute is JsonPropertyNameAttribute stjProperty)
{
return stjProperty.Name;
}
break;
}
#endif
default:
{
return prop.Name;
}
}
}

return prop.Name;
}
}
}
4 changes: 2 additions & 2 deletions src/JWT/JWT.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>
<TargetFrameworks>netstandard1.3;netstandard2.0;net6.0;net35;net40;net462;</TargetFrameworks>
Expand All @@ -20,7 +20,7 @@
<Authors>Alexander Batishchev, John Sheehan, Michael Lehenbauer</Authors>
<PackageTags>jwt;json;authorization</PackageTags>
<PackageLicenseExpression>CC0-1.0</PackageLicenseExpression>
<Version>10.0.1</Version>
<Version>10.0.2</Version>
<FileVersion>10.0.0.0</FileVersion>
<AssemblyVersion>10.0.0.0</AssemblyVersion>
<RootNamespace>JWT</RootNamespace>
Expand Down
110 changes: 110 additions & 0 deletions tests/JWT.Tests.Common/Builder/JwtBuilderDecodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,5 +433,115 @@ public void Decode_ToDictionary_Without_Serializer_Should_Throw_Exception()
action.Should()
.Throw<ArgumentNullException>();
}

[TestMethod]
public void Encode_Decode_ToJsonNetDecoratedType_Should_UseDecoratedName_Bug456()
{
var serializer = new JsonNetSerializer();
var alg = new NoneAlgorithm();

var token = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer);

var expected = new TestData.TestDataJsonNetDecorated
{
City = "Amsterdam",
};

var encoded = token.Encode(expected);
encoded.Should().NotBeNullOrEmpty();

token = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer);

var actual = token.Decode<TestData.TestDataJsonNetDecorated>(encoded);
actual.Should().BeEquivalentTo(expected);
}

[TestMethod]
public void Encode_Decode_Should_Return_Token_Nested_Data_ShouldRespectAttributes()
{
var serializer = new JsonNetSerializer();
var alg = new NoneAlgorithm();

var expected = new TestData.TestDataJsonNetDecorated
{
City = "Amsterdam",
};

var encoded = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer)
.AddClaim<TestData.TestDataJsonNetDecorated>("Data", expected)
.Encode();

encoded.Should().NotBeNullOrEmpty();
Console.WriteLine(encoded);
var jwtBuilder = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer);

var payloadType = new TestData.PayloadWithNestedJsonNetData
{
Data = default
};

var actual = (TestData.PayloadWithNestedJsonNetData)jwtBuilder.Decode(encoded, payloadType.GetType());
actual.Data.Should().BeEquivalentTo(expected);
}

#if NETSTANDARD2_0 || NET6_0_OR_GREATER
[TestMethod]
public void Encode_Decode_ToSystemTextSerializerDecoratedType_Should_UseDecoratedName_Bug456()
{
var serializer = new SystemTextSerializer();
var alg = new NoneAlgorithm();

var expected = new TestData.TestDataSystemTextSerializerDecorated
{
City = "Amsterdam",
};

var encoded = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer)
.Encode(expected);
encoded.Should().NotBeNullOrEmpty();

var actual = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer)
.Decode<TestData.TestDataSystemTextSerializerDecorated>(encoded);
actual.Should().BeEquivalentTo(expected);
}

[TestMethod]
public void Encode_Decode_Should_Return_Token_Nested_Data_ShouldRespectAttributes_SystemTextSerializer()
{
var serializer = new SystemTextSerializer();
var alg = new NoneAlgorithm();

var expected = new TestData.TestDataSystemTextSerializerDecorated
{
City = "Amsterdam",
};

var encoded = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer)
.AddClaim<TestData.TestDataSystemTextSerializerDecorated>("Data", expected)
.Encode();
encoded.Should().NotBeNullOrEmpty();

var actual = JwtBuilder.Create()
.WithAlgorithm(alg)
.WithJsonSerializer(serializer)
.WithJsonSerializer(serializer)
.Decode<TestData.PayloadWithNestedSystemTextSerializerData>(encoded);
actual.Data.Should().BeEquivalentTo(expected);
}
#endif
}
}
107 changes: 52 additions & 55 deletions tests/JWT.Tests.Common/Builder/JwtBuilderEncodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,78 +230,46 @@ public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload()
}

[TestMethod]
public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload_And_Claims()
public void Encode_Should_Throw_NotSupportedException_When_Using_EncodePayload_WithAddClaim()
{
const string key = TestData.Secret;

var token = JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.AddHeader("version", 1)
.AddClaim("ExtraClaim", "ValueClaim")
.Encode(TestData.Customer);

token.Should()
.Be(TestData.TokenWithCustomTypeHeader3AndClaim, "because the same data encoded with the same key must result in the same token");
}

[TestMethod]
public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload_And_Claims_With_Nested()
{
const string key = TestData.Secret;

var token = JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.AddHeader("version", 1)
.AddClaim("ExtraClaim", new { NestedProperty1 = "Foo", NestedProperty2 = 3 })
.Encode(TestData.Customer);

token.Should()
.Be(TestData.TokenWithCustomTypeHeader3AndClaimNested, "because the same data encoded with the same key must result in the same token");
}

[TestMethod]
public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload_And_Claims_With_Nested_TypesMatch()
{
const string key = TestData.Secret;

var token = JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.AddHeader("version", 1)
.AddClaim("ExtraClaim", new { NestedProperty1 = "Foo", NestedProperty2 = 3 })
.Encode(typeof(Customer), TestData.Customer);
Action action = () =>
JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.AddHeader("version", 1)
.AddClaim("ExtraClaim", "ValueClaim")
.Encode(TestData.Customer);

token.Should()
.Be(TestData.TokenWithCustomTypeHeader3AndClaimNested, "because the same data encoded with the same key must result in the same token");
action.Should()
.Throw<NotSupportedException>("because using both Encode(payload) and AddClaim is not supported");
}

[TestMethod]
public void Encode_Should_Return_ThrowTargetException_Encode_TypesMatch()
public void Encode_Should_Throw_NotSupportedException_When_Using_EncodePayload_WithAddClaims()
{
const string key = TestData.Secret;

Action action = () =>
{
var claims = new Dictionary<string, object>
{
{ "key" , "value" }
};

JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.AddHeader("version", 1)
.AddClaim("ExtraClaim", new { NestedProperty1 = "Foo", NestedProperty2 = 3 })
.Encode(typeof(string), TestData.Customer);
.AddClaims(claims)
.Encode(TestData.Customer);
};

if (IsRunningOnMono())
{
action.Should()
.Throw<TargetInvocationException>("Exception has been thrown by the target of an invocation.");
}
else
{
action.Should()
.Throw<TargetException>("Object does not match target type.");
}
action.Should()
.Throw<NotSupportedException>("because using both Encode(payload) and AddClaims() is not supported");
}

/// <summary>
/// Copied from: https://stackoverflow.com/a/7077620/2890855
/// </summary>
Expand Down Expand Up @@ -382,6 +350,35 @@ public void Encode_With_Secret_Should_Return_Valid_Token_Using_Json_Net()
}

#if NETSTANDARD2_0 || NET6_0_OR_GREATER

[TestMethod]
public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload_SystemTextSerializer()
{
var serializer = new SystemTextSerializer();

const string key = TestData.Secret;

var token = JwtBuilder.Create()
.WithAlgorithm(TestData.HMACSHA256Algorithm)
.WithSecret(key)
.WithJsonSerializer(serializer)
.AddHeader("version", 1)
.Encode(
new
{
ExtraClaim = new
{
NestedProperty1 = "Foo",
NestedProperty2 = 3
},
FirstName = "Jesus",
Age = 33
});

token.Should()
.Be(TestData.TokenWithCustomTypeHeader3AndClaimNested, "because the same data encoded with the same key must result in the same token");
}

[TestMethod]
public void Encode_Test_Bug438()
{
Expand Down
Loading