From e29580affe78881c1dbe83350904c32426bd3760 Mon Sep 17 00:00:00 2001 From: Ryan Rowland Date: Wed, 4 Nov 2020 18:06:23 -0600 Subject: [PATCH 1/2] In progress --- src/Twilio/JWT/AccessToken/Token.cs | 22 +++++++- .../Jwt/AccessToken/AccessTokenTest.cs | 35 +++++++++++-- test/Twilio.Test/Jwt/DecodedJwt.cs | 52 +++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/Twilio/JWT/AccessToken/Token.cs b/src/Twilio/JWT/AccessToken/Token.cs index bd3ca81dd..8f0a4eb30 100644 --- a/src/Twilio/JWT/AccessToken/Token.cs +++ b/src/Twilio/JWT/AccessToken/Token.cs @@ -13,6 +13,7 @@ public class Token : BaseJwt private readonly string _identity; private readonly DateTime? _nbf; private readonly HashSet _grants; + private readonly string _region; /// /// Create a new Access Token @@ -24,6 +25,7 @@ public class Token : BaseJwt /// Token expiration /// Token nbf /// Token grants + /// Token region public Token( string accountSid, string signingKeySid, @@ -31,7 +33,8 @@ public Token( string identity = null, DateTime? expiration = null, DateTime? nbf = null, - HashSet grants = null + HashSet grants = null, + string region = null ) : base(secret, signingKeySid, expiration.HasValue ? expiration.Value : DateTime.UtcNow.AddSeconds(3600)) { var now = BaseJwt.ConvertToUnixTimestamp(DateTime.UtcNow); @@ -40,6 +43,7 @@ public Token( this._identity = identity; this._nbf = nbf; this._grants = grants; + this._region = region; } /// @@ -75,6 +79,17 @@ public override DateTime? Nbf } } + /// + /// The region associated with this account + /// + public string Region + { + get + { + return _region; + } + } + /// /// Headers for an Access Token /// @@ -82,7 +97,10 @@ public override Dictionary Headers { get { - return new Dictionary { { "cty", "twilio-fpa;v=1" } }; + var headers = new Dictionary { { "cty", "twilio-fpa;v=1" } }; + + + return headers; } } diff --git a/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs b/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs index 503dd1c05..5cda5c4a8 100644 --- a/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs +++ b/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using NUnit.Framework; using Twilio.Jwt; @@ -16,14 +17,17 @@ public TestToken( string identity = null, DateTime? expiration = null, DateTime? nbf = null, - HashSet grants = null - ) : base(accountSid, signingKeySid, secret, identity, expiration, nbf, grants) {} + HashSet grants = null, + string region = null + ) : base(accountSid, signingKeySid, secret, identity, expiration, nbf, grants, region) {} public override Dictionary Headers { get { - return new Dictionary(); + var headers = new Dictionary(); + //headers.Add("cty", "twilio-fpa;v=1"); + return headers; } } } @@ -54,6 +58,31 @@ public void TestBuildToken() Assert.AreEqual("{}", payload["grants"].ToString()); } + [Test] + public void TestHaveRegion() + { + var now = DateTime.UtcNow; + var token = new TestToken("AC456", "SK123", Secret, region: "us1").ToJwt(); + Assert.IsNotNull(token); + Assert.IsNotEmpty(token); + + var decoded = new DecodedJwt(token, Secret); + var payload = decoded.Payload; + var header = decoded.Header; + Assert.IsNotNull(payload); + Assert.IsNotNull(header); + + NUnit.Framework.TestContext.Progress.WriteLine(header); + NUnit.Framework.TestContext.Progress.WriteLine(header["cty"]); + + Assert.AreEqual("SK123", payload["iss"]); + Assert.AreEqual("AC456", payload["sub"]); + Assert.AreEqual("twilio-fpa;v=1", header["cty"]); + Assert.AreEqual("us1", header["twr"]); + + Assert.AreEqual("{}", payload["grants"].ToString()); + } + [Test] public void TestHaveNbf() diff --git a/test/Twilio.Test/Jwt/DecodedJwt.cs b/test/Twilio.Test/Jwt/DecodedJwt.cs index 7521e48d3..cab7d1a3b 100644 --- a/test/Twilio.Test/Jwt/DecodedJwt.cs +++ b/test/Twilio.Test/Jwt/DecodedJwt.cs @@ -2,6 +2,8 @@ using System.IdentityModel.Tokens.Jwt; #else using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; #endif using System; @@ -21,6 +23,14 @@ public JwtPayload Payload } } + public JwtHeader Header + { + get + { + return token.Header; + } + } + private readonly JwtSecurityToken token; public DecodedJwt(string jwt, string secret) @@ -29,6 +39,46 @@ public DecodedJwt(string jwt, string secret) } #else + private static byte[] Base64UrlDecode(string input) => Convert.FromBase64String(UrlDecode(input)); + + private static string UrlDecode(string input) + { + var output = input; + output = output.Replace('-', '+'); // 62nd char of encoding + output = output.Replace('_', '/'); // 63rd char of encoding + + // Pad with trailing '='s + switch (output.Length % 4) + { + case 0: + break; // No pad chars in this case + case 2: + output += "=="; + break; // Two pad chars + case 3: + output += "="; + break; // One pad char + default: + throw new Exception($"Illegal base-64 string: '{input}'."); + } + + return output; + } + + public IDictionary Header + { + get + { + var parts = _jwt.Split('.'); + var header = parts[0]; + var headerBytes = Base64UrlDecode(header); + var headerJson = Encoding.UTF8.GetString(headerBytes); + var headerData = JsonConvert.DeserializeObject>(headerJson); + + return headerData; + } + } + public IDictionary Payload { get @@ -37,10 +87,12 @@ public IDictionary Payload } } + private readonly string _jwt; private readonly string token; public DecodedJwt(string jwt, string secret) { + _jwt = jwt; token = JWT.JsonWebToken.Decode(jwt, secret); } From 30581f7d84fe552f530f8f26203dd1a2a3deb306 Mon Sep 17 00:00:00 2001 From: charliesantos Date: Wed, 4 Nov 2020 18:35:59 -0800 Subject: [PATCH 2/2] Update decode class --- src/Twilio/JWT/AccessToken/Token.cs | 4 ++ .../Jwt/AccessToken/AccessTokenTest.cs | 42 ++++++------ test/Twilio.Test/Jwt/DecodedJwt.cs | 64 +++++-------------- 3 files changed, 42 insertions(+), 68 deletions(-) diff --git a/src/Twilio/JWT/AccessToken/Token.cs b/src/Twilio/JWT/AccessToken/Token.cs index 8f0a4eb30..0786ed39b 100644 --- a/src/Twilio/JWT/AccessToken/Token.cs +++ b/src/Twilio/JWT/AccessToken/Token.cs @@ -99,6 +99,10 @@ public override Dictionary Headers { var headers = new Dictionary { { "cty", "twilio-fpa;v=1" } }; + if (_region != null) + { + headers.Add("twr", _region); + } return headers; } diff --git a/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs b/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs index 5cda5c4a8..33af9de61 100644 --- a/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs +++ b/test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs @@ -20,16 +20,6 @@ public TestToken( HashSet grants = null, string region = null ) : base(accountSid, signingKeySid, secret, identity, expiration, nbf, grants, region) {} - - public override Dictionary Headers - { - get - { - var headers = new Dictionary(); - //headers.Add("cty", "twilio-fpa;v=1"); - return headers; - } - } } [TestFixture] @@ -62,28 +52,38 @@ public void TestBuildToken() public void TestHaveRegion() { var now = DateTime.UtcNow; - var token = new TestToken("AC456", "SK123", Secret, region: "us1").ToJwt(); + var token = new TestToken("AC456", "SK123", Secret, region: "foo").ToJwt(); Assert.IsNotNull(token); Assert.IsNotEmpty(token); var decoded = new DecodedJwt(token, Secret); - var payload = decoded.Payload; var header = decoded.Header; - Assert.IsNotNull(payload); Assert.IsNotNull(header); + Assert.AreEqual("twilio-fpa;v=1", header["cty"]); + Assert.AreEqual("foo", header["twr"]); + } - NUnit.Framework.TestContext.Progress.WriteLine(header); - NUnit.Framework.TestContext.Progress.WriteLine(header["cty"]); + [Test] + public void TestEmptyRegion() + { + var now = DateTime.UtcNow; + var token = new TestToken("AC456", "SK123", Secret).ToJwt(); + Assert.IsNotNull(token); + Assert.IsNotEmpty(token); - Assert.AreEqual("SK123", payload["iss"]); - Assert.AreEqual("AC456", payload["sub"]); + var decoded = new DecodedJwt(token, Secret); + var header = decoded.Header; + Assert.IsNotNull(header); Assert.AreEqual("twilio-fpa;v=1", header["cty"]); - Assert.AreEqual("us1", header["twr"]); - - Assert.AreEqual("{}", payload["grants"].ToString()); + + try { + var twr = header["twr"]; + Assert.Fail(); + } catch (KeyNotFoundException) { + // Pass + } } - [Test] public void TestHaveNbf() { diff --git a/test/Twilio.Test/Jwt/DecodedJwt.cs b/test/Twilio.Test/Jwt/DecodedJwt.cs index cab7d1a3b..73e0f4fd9 100644 --- a/test/Twilio.Test/Jwt/DecodedJwt.cs +++ b/test/Twilio.Test/Jwt/DecodedJwt.cs @@ -1,44 +1,13 @@ -#if !NET35 -using System.IdentityModel.Tokens.Jwt; -#else +using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; -#endif - -using System; using Newtonsoft.Json; namespace Twilio.Tests.Jwt { class DecodedJwt { -#if !NET35 - - public JwtPayload Payload - { - get - { - return token.Payload; - } - } - - public JwtHeader Header - { - get - { - return token.Header; - } - } - - private readonly JwtSecurityToken token; - - public DecodedJwt(string jwt, string secret) - { - token = new JwtSecurityToken(jwt); - } - -#else private static byte[] Base64UrlDecode(string input) => Convert.FromBase64String(UrlDecode(input)); private static string UrlDecode(string input) @@ -65,17 +34,20 @@ private static string UrlDecode(string input) return output; } + private static IDictionary DecodeJwtPart(string part) + { + var bytes = Base64UrlDecode(part); + var json = Encoding.UTF8.GetString(bytes); + var data = JsonConvert.DeserializeObject>(json); + + return data; + } + public IDictionary Header { get { - var parts = _jwt.Split('.'); - var header = parts[0]; - var headerBytes = Base64UrlDecode(header); - var headerJson = Encoding.UTF8.GetString(headerBytes); - var headerData = JsonConvert.DeserializeObject>(headerJson); - - return headerData; + return _header; } } @@ -83,20 +55,18 @@ public IDictionary Payload { get { - return JsonConvert.DeserializeObject>(token); + return _payload; } } - private readonly string _jwt; - private readonly string token; + private readonly IDictionary _header; + private readonly IDictionary _payload; public DecodedJwt(string jwt, string secret) { - _jwt = jwt; - token = JWT.JsonWebToken.Decode(jwt, secret); + var parts = jwt.Split('.'); + _header = DecodeJwtPart(parts[0]); + _payload = DecodeJwtPart(parts[1]); } - -#endif - } }