Skip to content

Commit

Permalink
feat: Add region to access token (#546)
Browse files Browse the repository at this point in the history
  • Loading branch information
liberty-rowland authored Nov 10, 2020
1 parent 5a45223 commit 69d82ef
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 32 deletions.
26 changes: 24 additions & 2 deletions src/Twilio/JWT/AccessToken/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Token : BaseJwt
private readonly string _identity;
private readonly DateTime? _nbf;
private readonly HashSet<IGrant> _grants;
private readonly string _region;

/// <summary>
/// Create a new Access Token
Expand All @@ -24,14 +25,16 @@ public class Token : BaseJwt
/// <param name="expiration">Token expiration</param>
/// <param name="nbf">Token nbf</param>
/// <param name="grants">Token grants</param>
/// <param name="region">Token region</param>
public Token(
string accountSid,
string signingKeySid,
string secret,
string identity = null,
DateTime? expiration = null,
DateTime? nbf = null,
HashSet<IGrant> grants = null
HashSet<IGrant> grants = null,
string region = null
) : base(secret, signingKeySid, expiration.HasValue ? expiration.Value : DateTime.UtcNow.AddSeconds(3600))
{
var now = BaseJwt.ConvertToUnixTimestamp(DateTime.UtcNow);
Expand All @@ -40,6 +43,7 @@ public Token(
this._identity = identity;
this._nbf = nbf;
this._grants = grants;
this._region = region;
}

/// <summary>
Expand Down Expand Up @@ -75,14 +79,32 @@ public override DateTime? Nbf
}
}

/// <summary>
/// The region associated with this account
/// </summary>
public string Region
{
get
{
return _region;
}
}

/// <summary>
/// Headers for an Access Token
/// </summary>
public override Dictionary<string, object> Headers
{
get
{
return new Dictionary<string, object> { { "cty", "twilio-fpa;v=1" } };
var headers = new Dictionary<string, object> { { "cty", "twilio-fpa;v=1" } };

if (_region != null)
{
headers.Add("twr", _region);
}

return headers;
}
}

Expand Down
49 changes: 39 additions & 10 deletions test/Twilio.Test/Jwt/AccessToken/AccessTokenTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Twilio.Jwt;
Expand All @@ -16,16 +17,9 @@ public TestToken(
string identity = null,
DateTime? expiration = null,
DateTime? nbf = null,
HashSet<IGrant> grants = null
) : base(accountSid, signingKeySid, secret, identity, expiration, nbf, grants) {}

public override Dictionary<string, object> Headers
{
get
{
return new Dictionary<string, object>();
}
}
HashSet<IGrant> grants = null,
string region = null
) : base(accountSid, signingKeySid, secret, identity, expiration, nbf, grants, region) {}
}

[TestFixture]
Expand Down Expand Up @@ -54,6 +48,41 @@ public void TestBuildToken()
Assert.AreEqual("{}", payload["grants"].ToString());
}

[Test]
public void TestHaveRegion()
{
var now = DateTime.UtcNow;
var token = new TestToken("AC456", "SK123", Secret, region: "foo").ToJwt();
Assert.IsNotNull(token);
Assert.IsNotEmpty(token);

var decoded = new DecodedJwt(token, Secret);
var header = decoded.Header;
Assert.IsNotNull(header);
Assert.AreEqual("twilio-fpa;v=1", header["cty"]);
Assert.AreEqual("foo", header["twr"]);
}

[Test]
public void TestEmptyRegion()
{
var now = DateTime.UtcNow;
var token = new TestToken("AC456", "SK123", Secret).ToJwt();
Assert.IsNotNull(token);
Assert.IsNotEmpty(token);

var decoded = new DecodedJwt(token, Secret);
var header = decoded.Header;
Assert.IsNotNull(header);
Assert.AreEqual("twilio-fpa;v=1", header["cty"]);

try {
var twr = header["twr"];
Assert.Fail();
} catch (KeyNotFoundException) {
// Pass
}
}

[Test]
public void TestHaveNbf()
Expand Down
62 changes: 42 additions & 20 deletions test/Twilio.Test/Jwt/DecodedJwt.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,72 @@
#if !NET35
using System.IdentityModel.Tokens.Jwt;
#else
using System;
using System.Collections.Generic;
#endif

using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

namespace Twilio.Tests.Jwt
{
class DecodedJwt
{
#if !NET35
private static byte[] Base64UrlDecode(string input) => Convert.FromBase64String(UrlDecode(input));

public JwtPayload Payload
private static string UrlDecode(string input)
{
get
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)
{
return token.Payload;
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;
}

private readonly JwtSecurityToken token;
private static IDictionary<string, object> DecodeJwtPart(string part)
{
var bytes = Base64UrlDecode(part);
var json = Encoding.UTF8.GetString(bytes);
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);

public DecodedJwt(string jwt, string secret)
return data;
}

public IDictionary<string, object> Header
{
token = new JwtSecurityToken(jwt);
get
{
return _header;
}
}

#else
public IDictionary<string, object> Payload
{
get
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(token);
return _payload;
}
}

private readonly string token;
private readonly IDictionary<string, object> _header;
private readonly IDictionary<string, object> _payload;

public DecodedJwt(string jwt, string secret)
{
token = JWT.JsonWebToken.Decode(jwt, secret);
var parts = jwt.Split('.');
_header = DecodeJwtPart(parts[0]);
_payload = DecodeJwtPart(parts[1]);
}

#endif

}
}

0 comments on commit 69d82ef

Please sign in to comment.