diff --git a/src/dotnet-decode-jwt/ClaimsDisplayer.cs b/src/dotnet-decode-jwt/ClaimsDisplayer.cs index cdcc1b4..0086d97 100644 --- a/src/dotnet-decode-jwt/ClaimsDisplayer.cs +++ b/src/dotnet-decode-jwt/ClaimsDisplayer.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Encodings.Web; namespace DotNet.Decode.Jwt; @@ -13,6 +12,11 @@ public class ClaimsDisplayer private const string IssuedAtKeyName = "iat"; private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly JsonSerializerOptions SerializationOptions = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; public ClaimsDisplayer(IConsole console, TimeZoneInfo localTimeZone) { @@ -20,11 +24,11 @@ public ClaimsDisplayer(IConsole console, TimeZoneInfo localTimeZone) _localTimeZone = localTimeZone; } - public void DisplayClaims(JObject claims) + public void DisplayClaims(JsonElement claims) { try { - if (claims.Count == 0) + if (claims.ValueKind == JsonValueKind.Undefined) { _console.ForegroundColor = ConsoleColor.DarkGray; _console.WriteLine("There was no claims in the JWT."); @@ -42,7 +46,7 @@ public void DisplayClaims(JObject claims) _console.WriteLine(string.Empty); _console.ResetColor(); - _console.WriteLine(JsonConvert.SerializeObject(claims, Formatting.Indented)); + _console.WriteLine(JsonSerializer.Serialize(claims, SerializationOptions)); } } finally @@ -51,11 +55,17 @@ public void DisplayClaims(JObject claims) } } - private string FormatDateTime(JObject claims, string key) + private string FormatDateTime(JsonElement claims, string key) { - if (!claims.TryGetValue(key, out var token)) return "N/A"; + if (!claims.TryGetProperty(key, out var token)) + { + return "N/A"; + } - var timestamp = token.Value(); + if (token.ValueKind != JsonValueKind.Number || !token.TryGetInt32(out var timestamp)) + { + return "N/A"; + } var utcTime = Epoch.AddSeconds(timestamp); diff --git a/src/dotnet-decode-jwt/JwtClaimsDecoder.cs b/src/dotnet-decode-jwt/JwtClaimsDecoder.cs index f1663e3..b4686ba 100644 --- a/src/dotnet-decode-jwt/JwtClaimsDecoder.cs +++ b/src/dotnet-decode-jwt/JwtClaimsDecoder.cs @@ -1,21 +1,18 @@ -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace DotNet.Decode.Jwt; +namespace DotNet.Decode.Jwt; public static class JwtClaimsDecoder { - public static JObject GetClaims(string jwt) + public static JsonElement GetClaims(string jwt) { var base64UrlClaimsSet = GetBase64UrlClaimsSet(jwt); var claimsSet = DecodeBase64Url(base64UrlClaimsSet); try { - return JObject.Parse(claimsSet); + using var jsonDocument = JsonDocument.Parse(claimsSet); + return jsonDocument.RootElement.Clone(); } - catch (JsonReaderException e) + catch (Exception e) { throw new FormatException(e.Message, e); } diff --git a/src/dotnet-decode-jwt/dotnet-decode-jwt.csproj b/src/dotnet-decode-jwt/dotnet-decode-jwt.csproj index 56e738f..fb462e9 100644 --- a/src/dotnet-decode-jwt/dotnet-decode-jwt.csproj +++ b/src/dotnet-decode-jwt/dotnet-decode-jwt.csproj @@ -20,7 +20,10 @@ snupkg - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,6 +33,6 @@ README.md - - + + diff --git a/tests/dotnet-decode-jwt-tests/ClaimsDisplayerTests.cs b/tests/dotnet-decode-jwt-tests/ClaimsDisplayerTests.cs index 22ba132..928d511 100644 --- a/tests/dotnet-decode-jwt-tests/ClaimsDisplayerTests.cs +++ b/tests/dotnet-decode-jwt-tests/ClaimsDisplayerTests.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Runtime.InteropServices; -using FluentAssertions; namespace DotNet.Decode.Jwt.Tests; @@ -43,7 +42,7 @@ static ClaimsDisplayerTests() public void GivenNoClaim_WhenDisplayClaims_ThenMessage() { // Arrange - var claims = new JObject(); + var claims = new JsonElement(); // Act _target.DisplayClaims(claims); @@ -63,14 +62,10 @@ public void GivenNoClaim_WhenDisplayClaims_ThenMessage() public void GivenAnyClaim_WhenDisplayClaims_ThenDisplayClaims() { // Arrange - var claims = JObject.Parse(@" - { - 'iat': 1516239022 - } - "); + var claims = JsonDocument.Parse(@"{""iat"":1516239022}"); // Act - _target.DisplayClaims(claims); + _target.DisplayClaims(claims.RootElement); // Assert var expected = new List @@ -91,6 +86,64 @@ public void GivenAnyClaim_WhenDisplayClaims_ThenDisplayClaims() _console.Actions.Should().BeEquivalentTo(expected); } + + [Fact] + public void GivenIatIsNotTimestamp_WhenDisplayClaims_ThenDisplayDateAsNotAvailable() + { + // Arrange + var claims = JsonDocument.Parse(@"{""iat"":""hello""}"); + + // Act + _target.DisplayClaims(claims.RootElement); + + // Assert + var expected = new List + { + "WRITE: ", + "SET FOREGROUND COLOR: Yellow", + "WRITE: Expiration Time (exp): N/A", + "WRITE: Not Before (nbf): N/A", + "WRITE: Issued At (iat): N/A", + "SET FOREGROUND COLOR: Green", + "WRITE: ", + "WRITE: Claims are:", + "WRITE: ", + "RESET COLOR", + $"WRITE: {{{Environment.NewLine} \"iat\": \"hello\"{Environment.NewLine}}}", + "RESET COLOR" + }; + + _console.Actions.Should().BeEquivalentTo(expected); + } + + [Fact] + public void GivenHtmlSensitiveCharacter_WhenDisplayClaims_ThenDoNotEscape() + { + // Arrange + var claims = JsonDocument.Parse(@"{""hi"":""I'm""}"); + + // Act + _target.DisplayClaims(claims.RootElement); + + // Assert + var expected = new List + { + "WRITE: ", + "SET FOREGROUND COLOR: Yellow", + "WRITE: Expiration Time (exp): N/A", + "WRITE: Not Before (nbf): N/A", + "WRITE: Issued At (iat): N/A", + "SET FOREGROUND COLOR: Green", + "WRITE: ", + "WRITE: Claims are:", + "WRITE: ", + "RESET COLOR", + $"WRITE: {{{Environment.NewLine} \"hi\": \"I'm\"{Environment.NewLine}}}", + "RESET COLOR" + }; + + _console.Actions.Should().BeEquivalentTo(expected); + } } internal class MockConsole : IConsole diff --git a/tests/dotnet-decode-jwt-tests/JwtClaimsDecoderTests.cs b/tests/dotnet-decode-jwt-tests/JwtClaimsDecoderTests.cs index 72d7565..1e4116c 100644 --- a/tests/dotnet-decode-jwt-tests/JwtClaimsDecoderTests.cs +++ b/tests/dotnet-decode-jwt-tests/JwtClaimsDecoderTests.cs @@ -48,15 +48,10 @@ public void GivenIatIsNumber_WhenGetClaims_ThenReturnClaims() var actualClaims = JwtClaimsDecoder.GetClaims(jwt); // Assert - var expectedClaims = JObject.Parse(@" - { - 'sub': '1234567890', - 'name': 'John Doe', - 'iat': 1516239022 - } - "); - - Assert.Equal(expectedClaims, actualClaims); + var expectedClaims = + JsonDocument.Parse(@"{""sub"":""1234567890"",""name"":""John Doe"",""iat"":1516239022}"); + + actualClaims.GetRawText().Should().Be(expectedClaims.RootElement.GetRawText()); } [Fact] @@ -69,13 +64,9 @@ public void GivenAudIsArrayOfString_WhenGetClaims_ThenReturnClaims() var actualClaims = JwtClaimsDecoder.GetClaims(jwt); // Assert - var expectedClaims = JObject.Parse(@" - { - 'aud': ['audience-one','audience-two'] - } - "); + var expectedClaims = JsonDocument.Parse(@"{""aud"":[""audience-one"",""audience-two""]}"); - Assert.Equal(expectedClaims, actualClaims); + actualClaims.GetRawText().Should().Be(expectedClaims.RootElement.GetRawText()); } [Fact] @@ -88,31 +79,39 @@ public void GivenAudIsSingleString_WhenGetClaims_ThenReturnClaims() var actualClaims = JwtClaimsDecoder.GetClaims(jwt); // Assert - var expectedClaims = JObject.Parse(@" - { - 'aud': 'audience' - } - "); + var expectedClaims = JsonDocument.Parse(@"{""aud"":""audience""}"); + + actualClaims.GetRawText().Should().Be(expectedClaims.RootElement.GetRawText()); + } + + [Fact] + public void GivenHtmlSensitiveCharacter_WhenGetClaims_ThenReturnUnescapedClaims() + { + // Arrange + const string jwt = "eyJhbGciOiJub25lIn0.eyJoaSI6IkknbSJ9."; + + // Act + var actualClaims = JwtClaimsDecoder.GetClaims(jwt); + + // Assert + var expectedClaims = JsonDocument.Parse(@"{""hi"":""I'm""}"); - Assert.Equal(expectedClaims, actualClaims); + actualClaims.GetRawText().Should().Be(expectedClaims.RootElement.GetRawText()); } [Fact] public void GivenClaimKeyIsXmlNamespace_WhenGetClaims_ThenReturnClaims() { // Arrange - const string jwt = "eyJhbGciOiJub25lIn0.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiAiaGlAbWUuY29tIn0=."; + const string jwt = "eyJhbGciOiJub25lIn0.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJoaUBtZS5jb20ifQ==."; // Act var actualClaims = JwtClaimsDecoder.GetClaims(jwt); // Assert - var expectedClaims = JObject.Parse(@" - { - 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'hi@me.com' - } - "); + var expectedClaims = JsonDocument.Parse( + @"{""http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"":""hi@me.com""}"); - Assert.Equal(expectedClaims, actualClaims); + actualClaims.GetRawText().Should().Be(expectedClaims.RootElement.GetRawText()); } } diff --git a/tests/dotnet-decode-jwt-tests/dotnet-decode-jwt-tests.csproj b/tests/dotnet-decode-jwt-tests/dotnet-decode-jwt-tests.csproj index 7dc691a..deb7988 100644 --- a/tests/dotnet-decode-jwt-tests/dotnet-decode-jwt-tests.csproj +++ b/tests/dotnet-decode-jwt-tests/dotnet-decode-jwt-tests.csproj @@ -7,13 +7,13 @@ false - + + - all