Skip to content

Commit

Permalink
WIP JwtEncoder.Encode() now accepts a list of payloads so we can use …
Browse files Browse the repository at this point in the history
…both claims and payload
  • Loading branch information
hartmark committed Jan 25, 2023
1 parent 81507c7 commit 341da1c
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 63 deletions.
51 changes: 6 additions & 45 deletions src/JWT/Builder/JwtBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,18 +278,16 @@ public string Encode(Type payloadType, object payload)
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 => GetPropName(prop), prop => prop.GetValue(payload, null));

foreach (var pair in dic)
// Preserve old behaviour checking the type, is it needed?
if (payloadType != payload.GetType())
{
_jwt.Payload.Add(pair.Key, pair.Value);
throw new NotSupportedException("Object does not match target type.");
}

EnsureCanEncode();

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

/// <summary>
Expand Down Expand Up @@ -482,42 +480,5 @@ 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;
}
}
}
8 changes: 8 additions & 0 deletions src/JWT/IJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public interface IJsonSerializer
/// <param name="json">The JSON string deserialize.</param>
/// <returns>Strongly-typed object.</returns>
object Deserialize(Type type, string json);

/// <summary>
/// Merges two objects into a single JSON string.
/// </summary>
/// <param name="obj1">The first object to serialize</param>
/// <param name="obj2">The second object to serialize, note that any existing property in obj1 will be
/// <returns>JSON string</returns>
string MergeObjects(object obj1, object obj2);
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/JWT/IJwtEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface IJwtEncoder
/// <param name="key">The key bytes used to sign the token</param>
/// <returns>The generated JWT</returns>
string Encode(IDictionary<string, object> extraHeaders, object payload, byte[] key);

string Encode(IDictionary<string, object> extraHeaders, object[] payloads, byte[] key);
}

/// <summary>
Expand Down
41 changes: 41 additions & 0 deletions src/JWT/JwtEncoder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JWT.Algorithms;

using static JWT.Internal.EncodingHelper;
Expand Down Expand Up @@ -39,6 +40,46 @@ public JwtEncoder(IJwtAlgorithm algorithm, IJsonSerializer jsonSerializer, IBase
{
}

public string Encode(IDictionary<string, object> extraHeaders, object[] payloads, byte[] key)
{
if (payloads is null)
throw new ArgumentNullException(nameof(payloads));

if (!payloads.Any() || payloads.Length != 2)
{
throw new NotSupportedException("Only two payloads are supported");
}

var algorithm = _algFactory.Create(null);
if (algorithm is null)
throw new ArgumentNullException(nameof(algorithm));
if (!algorithm.IsAsymmetric() && key is null && algorithm is not NoneAlgorithm)
throw new ArgumentNullException(nameof(key));

var header = extraHeaders is null ?
new Dictionary<string, object>(2, StringComparer.OrdinalIgnoreCase) :
new Dictionary<string, object>(extraHeaders, StringComparer.OrdinalIgnoreCase);

if (!header.ContainsKey("typ"))
{
header.Add("typ", "JWT");
}
header.Add("alg", algorithm.Name);

var headerBytes = GetBytes(_jsonSerializer.Serialize(header));

var payloadBytes = GetBytes(_jsonSerializer.MergeObjects(payloads[0], payloads[1]));

var headerSegment = _urlEncoder.Encode(headerBytes);
var payloadSegment = _urlEncoder.Encode(payloadBytes);

var stringToSign = headerSegment + "." + payloadSegment;
var bytesToSign = GetBytes(stringToSign);

var signatureSegment = GetSignatureSegment(algorithm, key, bytesToSign);
return stringToSign + "." + signatureSegment;
}

/// <inheritdoc />
/// <exception cref="ArgumentNullException" />
public string Encode(IDictionary<string, object> extraHeaders, object payload, byte[] key)
Expand Down
10 changes: 10 additions & 0 deletions src/JWT/Serializers/JsonNetSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JWT.Serializers
{
Expand Down Expand Up @@ -58,5 +59,14 @@ public object Deserialize(Type type, string json)
using var jsonReader = new JsonTextReader(stringReader);
return _serializer.Deserialize(jsonReader, type);
}

public string MergeObjects(object obj1, object obj2)
{
var jObject1 = JObject.Parse(Serialize(obj1));
var jObject2 = JObject.Parse(Serialize(obj2));

jObject1.Merge(jObject2);
return jObject1.ToString(Formatting.None);
}
}
}
28 changes: 27 additions & 1 deletion src/JWT/Serializers/SystemTextSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#if MODERN_DOTNET
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using JWT.Serializers.Converters;

namespace JWT.Serializers
Expand All @@ -19,7 +21,7 @@ public class SystemTextSerializer : IJsonSerializer
new DictionaryStringObjectJsonConverterCustomWrite()
}
};

/// <inheritdoc />
/// <exception cref="ArgumentNullException" />
public string Serialize(object obj)
Expand All @@ -43,6 +45,30 @@ public object Deserialize(Type type, string json)

return JsonSerializer.Deserialize(json, type, _optionsForDeserialize);
}

public string MergeObjects(object obj1, object obj2)
{
if (obj1 == null)
{
throw new ArgumentNullException(nameof(obj1));
}

if (obj2 == null)
{
throw new ArgumentNullException(nameof(obj2));
}

var jsonNode1 = JsonNode.Parse(Serialize(obj1));
var jsonNode2 = JsonNode.Parse(Serialize(obj2));

foreach (var property in jsonNode2.AsObject().ToArray())
{
jsonNode2.AsObject().Remove(property.Key);
jsonNode1[property.Key] = property.Value;
}

return jsonNode1.ToJsonString(_optionsForSerialize);
}
}
}
#endif
19 changes: 2 additions & 17 deletions tests/JWT.Tests.Common/Builder/JwtBuilderEncodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,25 +290,10 @@ public void Encode_Should_Return_ThrowTargetException_Encode_TypesMatch()
.AddClaim("ExtraClaim", new { NestedProperty1 = "Foo", NestedProperty2 = 3 })
.Encode(typeof(string), 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>("Object does not match target type.");
}

/// <summary>
/// Copied from: https://stackoverflow.com/a/7077620/2890855
/// </summary>
/// <returns></returns>
private static bool IsRunningOnMono() =>
Type.GetType("Mono.Runtime") is not null;

[TestMethod]
public void Encode_Should_Return_Token_With_Custom_Extra_Headers_Full_Payload2()
{
Expand Down

0 comments on commit 341da1c

Please sign in to comment.