Skip to content

Commit

Permalink
Refactor.
Browse files Browse the repository at this point in the history
  • Loading branch information
pmaytak committed Jun 18, 2024
1 parent eb4b024 commit fb3639d
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 40 deletions.
42 changes: 26 additions & 16 deletions src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,35 @@ internal class JsonClaimSet

internal object _claimsLock = new();
internal readonly Dictionary<string, object> _jsonClaims;
internal readonly Dictionary<string, (int startIndex, int length)> _jsonClaimsUtf8;
#if NET8_0_OR_GREATER
internal readonly Dictionary<string, (int startIndex, int length)?> _jsonClaimsBytes;
internal readonly Memory<byte> _tokenAsMemory;
#endif
private List<Claim> _claims;

internal JsonClaimSet()
{
_jsonClaims = new();
_jsonClaimsUtf8 = new();
#if NET8_0_OR_GREATER
_jsonClaimsBytes = new();
#endif
}

internal JsonClaimSet(Dictionary<string, object> jsonClaims)
{
_jsonClaims = jsonClaims;
}

#if NET8_0_OR_GREATER
internal JsonClaimSet(
Dictionary<string, object> jsonClaims,
Dictionary<string, (int startIndex, int length)> jsonClaimsUtf8,
Dictionary<string, (int startIndex, int length)?> jsonClaimsBytes,
Memory<byte> tokenAsMemory)
{
_jsonClaims = jsonClaims;
_jsonClaimsUtf8 = jsonClaimsUtf8;
_jsonClaimsBytes = jsonClaimsBytes;
_tokenAsMemory = tokenAsMemory;
}

#endif
internal List<Claim> Claims(string issuer)
{
if (_claims == null)
Expand Down Expand Up @@ -176,10 +180,13 @@ internal Claim GetClaim(string key, string issuer)

internal string GetStringValue(string key)
{
#if NET7_0_OR_GREATER
if (_jsonClaimsUtf8.TryGetValue(key, out (int, int) tuple))
#if NET8_0_OR_GREATER
if (_jsonClaimsBytes.TryGetValue(key, out (int StartIndex, int Length)? location))
{
return Encoding.UTF8.GetString(_tokenAsMemory.Slice(tuple.Item1, tuple.Item2).Span);
if (!location.HasValue)
return null;

return Encoding.UTF8.GetString(_tokenAsMemory.Slice(location.Value.StartIndex, location.Value.Length).Span);
}
#else
if (_jsonClaims.TryGetValue(key, out object obj))
Expand All @@ -194,13 +201,16 @@ internal string GetStringValue(string key)
return string.Empty;
}

#if NET7_0_OR_GREATER
internal ReadOnlySpan<byte> GetUtf8Bytes(string key)
#if NET8_0_OR_GREATER
// Similar to GetStringValue but returns the bytes directly.
internal ReadOnlySpan<byte> GetStringBytesValue(string key)
{
// (int startIndex, int length) tuple
if (_jsonClaimsUtf8.TryGetValue(key, out (int, int) tuple))
if (_jsonClaimsBytes.TryGetValue(key, out (int StartIndex, int Length)? location))
{
return _tokenAsMemory.Slice(tuple.Item1, tuple.Item2).Span;
if (!location.HasValue)
return null;

return _tokenAsMemory.Slice(location.Value.StartIndex, location.Value.Length).Span;
}

return new Span<byte>();
Expand Down Expand Up @@ -459,10 +469,10 @@ internal bool TryGetClaim(string key, string issuer, out Claim claim)
/// <returns></returns>
internal bool TryGetValue<T>(string key, out T value)
{
#if NET7_0_OR_GREATER
#if NET8_0_OR_GREATER
if (typeof(T) == typeof(string))
{
var span = GetUtf8Bytes(key);
var span = GetStringBytesValue(key);
if (!span.IsEmpty)
{
value = (T)(object)Encoding.UTF8.GetString(span);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ internal JsonClaimSet CreatePayloadClaimSet(Memory<byte> tokenPayloadAsMemory)
LogHelper.MarkAsNonPII(reader.BytesConsumed))));

Dictionary<string, object> claims = [];
Dictionary<string, (int startIndex, int length)> claimsUtf8 = [];
#if NET8_0_OR_GREATER
Dictionary<string, (int startIndex, int length)?> claimsBytes = [];
#endif
while (true)
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
ReadPayloadValue(ref reader, claims, claimsUtf8, tokenPayloadAsMemory);
#if NET8_0_OR_GREATER
ReadPayloadValue(ref reader, claims, claimsBytes, tokenPayloadAsMemory);
#else
ReadPayloadValue(ref reader, claims);
#endif
}
// We read a JsonTokenType.StartObject above, exiting and positioning reader at next token.
else if (JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false))
Expand All @@ -47,20 +53,15 @@ internal JsonClaimSet CreatePayloadClaimSet(Memory<byte> tokenPayloadAsMemory)
break;
};

return new JsonClaimSet(claims, claimsUtf8, tokenPayloadAsMemory);
#if NET8_0_OR_GREATER
return new JsonClaimSet(claims, claimsBytes, tokenPayloadAsMemory);
#else
return new JsonClaimSet(claims);
#endif
}

/// <summary>
///
/// </summary>
private protected virtual void ReadPayloadValue(
ref Utf8JsonReader reader,
Dictionary<string, object> claims,
Dictionary<string, (int startIndex, int length)> claimsUtf8,
Memory<byte> tokenAsMemory)
private protected virtual void ReadPayloadValue(ref Utf8JsonReader reader, IDictionary<string, object> claims)
{
_ = claims ?? throw new ArgumentNullException(nameof(claims));

if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
{
_audiences = [];
Expand Down Expand Up @@ -127,5 +128,77 @@ private protected virtual void ReadPayloadValue(
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
}
}

#if NET8_0_OR_GREATER
private protected virtual void ReadPayloadValue(
ref Utf8JsonReader reader,
Dictionary<string, object> claims,
Dictionary<string, (int startIndex, int length)?> claimsBytes,
Memory<byte> tokenAsMemory)
{
if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Aud))
{
_audiences = [];
reader.Read();
if (reader.TokenType == JsonTokenType.StartArray)
{
JsonSerializerPrimitives.ReadStringsSkipNulls(ref reader, _audiences, JwtRegisteredClaimNames.Aud, ClassName);
claims[JwtRegisteredClaimNames.Aud] = _audiences;
}
else
{
if (reader.TokenType != JsonTokenType.Null)
{
_audiences.Add(JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Aud, ClassName));
claims[JwtRegisteredClaimNames.Aud] = _audiences[0];
}
else
{
claims[JwtRegisteredClaimNames.Aud] = _audiences;
}
}
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Azp))
{
claimsBytes[JwtRegisteredClaimNames.Azp] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Azp, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Exp))
{
_exp = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Exp, ClassName, true);
_expDateTime = EpochTime.DateTime(_exp.Value);
claims[JwtRegisteredClaimNames.Exp] = _exp;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iat))
{
_iat = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Iat, ClassName, true);
_iatDateTime = EpochTime.DateTime(_iat.Value);
claims[JwtRegisteredClaimNames.Iat] = _iat;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
{
claimsBytes[JwtRegisteredClaimNames.Iss] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Iss, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
{
claimsBytes[JwtRegisteredClaimNames.Jti] = JsonSerializerPrimitives.ReadStringBytesLocation(ref reader, tokenAsMemory, JwtRegisteredClaimNames.Jti, ClassName, true);
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Nbf))
{
_nbf = JsonSerializerPrimitives.ReadLong(ref reader, JwtRegisteredClaimNames.Nbf, ClassName, true);
_nbfDateTime = EpochTime.DateTime(_nbf.Value);
claims[JwtRegisteredClaimNames.Nbf] = _nbf;
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Sub))
{
_sub = JsonSerializerPrimitives.ReadStringOrNumberAsString(ref reader, JwtRegisteredClaimNames.Sub, ClassName, true);
claims[JwtRegisteredClaimNames.Sub] = _sub;
}
else
{
string propertyName = reader.GetString();
claims[propertyName] = JsonSerializerPrimitives.ReadPropertyValueAsObject(ref reader, propertyName, JsonClaimSet.ClassName, true);
}
}
#endif
}
}
60 changes: 60 additions & 0 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1128,5 +1128,65 @@ internal DateTime? ValidToNullable
}
}
#endregion

#region Payload Properties Bytes
#if NET8_0_OR_GREATER

/// <summary>
/// Gets the 'azp' claim from the payload.
/// </summary>
/// <remarks>
/// Identifies the authorized party for the id_token.
/// see: https://openid.net/specs/openid-connect-core-1_0.html
/// <para>
/// If the 'azp' claim is not found, an empty string is returned.
/// </para>
/// </remarks>
public ReadOnlySpan<byte> AzpBytes
{
get
{
return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Azp);
}
}

/// <summary>
/// Gets the 'value' of the 'jti' claim from the payload.
/// </summary>
/// <remarks>
/// Provides a unique identifier for the JWT.
/// see: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
/// <para>
/// If the 'jti' claim is not found, an empty string is returned.
/// </para>
/// </remarks>
public ReadOnlySpan<byte> IdBytes
{
get
{
return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Jti);
}
}

/// <summary>
/// Gets the 'value' of the 'iss' claim from the payload.
/// </summary>
/// <remarks>
/// Identifies the principal that issued the JWT.
/// see: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
/// <para>
/// If the 'iss' claim is not found, an empty string is returned.
/// </para>
/// </remarks>
public ReadOnlySpan<byte> IssuerBytes
{
get
{
return Payload.GetStringBytesValue(JwtRegisteredClaimNames.Iss);
}
}

#endif
#endregion
}
}
Loading

0 comments on commit fb3639d

Please sign in to comment.