Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Adding jwt token expiry check function #754

Merged
merged 4 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 30 additions & 23 deletions src/Twilio/Base/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,36 +125,43 @@ public static Page<T> FromJson(string recordKey, string json)
var parsedRecords = records.Children().Select(
record => JsonConvert.DeserializeObject<T>(record.ToString())
).ToList();

if(root["uri"] != null){
var uriNode = root["uri"];
if (uriNode != null)
{
JToken pageSize;
JToken firstPageUri;
JToken nextPageUri;
JToken previousPageUri;

var uriNode = root["uri"];
if (uriNode != null)
{
JToken pageSize;
JToken firstPageUri;
JToken nextPageUri;
JToken previousPageUri;
// v2010 API
return new Page<T>(
parsedRecords,
root.TryGetValue("page_size", out pageSize) ? root["page_size"].Value<int>() : parsedRecords.Count,
uri: uriNode.Value<string>(),
firstPageUri: root.TryGetValue("first_page_uri", out firstPageUri) ? root["first_page_uri"].Value<string>() : null,
nextPageUri: root.TryGetValue("next_page_uri", out nextPageUri) ? root["next_page_uri"].Value<string>() : null,
previousPageUri: root.TryGetValue("previous_page_uri", out previousPageUri) ? root["previous_page_uri"].Value<string>() : null
);
}
}

// v2010 API
// next-gen API
if(root["meta"] != null){
var meta = root["meta"];
return new Page<T>(
parsedRecords,
root.TryGetValue("page_size", out pageSize) ? root["page_size"].Value<int>() : parsedRecords.Count,
uri: uriNode.Value<string>(),
firstPageUri: root.TryGetValue("first_page_uri", out firstPageUri) ? root["first_page_uri"].Value<string>() : null,
nextPageUri: root.TryGetValue("next_page_uri", out nextPageUri) ? root["next_page_uri"].Value<string>() : null,
previousPageUri: root.TryGetValue("previous_page_uri", out previousPageUri) ? root["previous_page_uri"].Value<string>() : null
meta["page_size"].Value<int>(),
url: meta["url"].Value<string>(),
firstPageUrl: meta["first_page_url"].Value<string>(),
nextPageUrl: meta["next_page_url"].Value<string>(),
previousPageUrl: meta["previous_page_url"].Value<string>()
);
}

// next-gen API
var meta = root["meta"];
return new Page<T>(
parsedRecords,
meta["page_size"].Value<int>(),
url: meta["url"].Value<string>(),
firstPageUrl: meta["first_page_url"].Value<string>(),
nextPageUrl: meta["next_page_url"].Value<string>(),
previousPageUrl: meta["previous_page_url"].Value<string>()
);
return new Page<T>(parsedRecords, 0, null, null, null, null);

}
}
}
45 changes: 45 additions & 0 deletions src/Twilio/ClientProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Twilio.Clients;
using Twilio.Clients.BearerToken;
using Twilio.Exceptions;
using Twilio.Http.BearerToken;

namespace Twilio
{
public class ClientProperties
{
public static string region;
public static string edge;
public static string logLevel;

public ClientProperties() { }


/// <summary>
/// Set the client region
/// </summary>
/// <param name="region">Client region</param>
public static void SetRegion(string region)
{
region = region;

Check warning on line 23 in src/Twilio/ClientProperties.cs

View workflow job for this annotation

GitHub Actions / Test

Assignment made to same variable; did you mean to assign something else?
}

/// <summary>
/// Set the client edge
/// </summary>
/// <param name="edge">Client edge</param>
public static void SetEdge(string edge)
{
edge = edge;

Check warning on line 32 in src/Twilio/ClientProperties.cs

View workflow job for this annotation

GitHub Actions / Test

Assignment made to same variable; did you mean to assign something else?
}

/// <summary>
/// Set the logging level
/// </summary>
/// <param name="loglevel">log level</param>
public static void SetLogLevel(string loglevel)
{
logLevel = loglevel;
}

}
}
30 changes: 30 additions & 0 deletions src/Twilio/Clients/Base64UrlEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#if NET35
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Script.Serialization;

Check failure on line 5 in src/Twilio/Clients/Base64UrlEncoder.cs

View workflow job for this annotation

GitHub Actions / Test

The type or namespace name 'Script' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)

Check failure on line 5 in src/Twilio/Clients/Base64UrlEncoder.cs

View workflow job for this annotation

GitHub Actions / Test

The type or namespace name 'Script' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)

namespace Twilio.Clients{

public abstract class Base64UrlEncoder
{
public static string Decode(string base64Url)
{
// Replace URL-safe characters with Base64 characters
string base64 = base64Url
.Replace('-', '+')
.Replace('_', '/');

// Add padding if necessary
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}

byte[] bytes = Convert.FromBase64String(base64);
return Encoding.UTF8.GetString(bytes);
}
}
}
#endif
119 changes: 96 additions & 23 deletions src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@

using System;
using System;
using System.Net;
using System.Linq;
using Newtonsoft.Json;
using Twilio.Exceptions;
using Twilio.Http.BearerToken;
using Twilio.Jwt;

using Twilio.Clients;

#if !NET35
using System.IdentityModel.Tokens.Jwt;
using System.Threading.Tasks;
#endif

using Twilio.Http;
using Twilio.Http.BearerToken;

Check warning on line 16 in src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs

View workflow job for this annotation

GitHub Actions / Test

The using directive for 'Twilio.Http.BearerToken' appeared previously in this namespace

Check warning on line 16 in src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs

View workflow job for this annotation

GitHub Actions / Test

The using directive for 'Twilio.Http.BearerToken' appeared previously in this namespace

Check warning on line 16 in src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs

View workflow job for this annotation

GitHub Actions / Test

The using directive for 'Twilio.Http.BearerToken' appeared previously in this namespace
#if NET35
using Twilio.Http.Net35;
using System.Collections.Generic;
using System.Text;
using System.Web.Script.Serialization;

Check failure on line 21 in src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs

View workflow job for this annotation

GitHub Actions / Test

The type or namespace name 'Script' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)

Check failure on line 21 in src/Twilio/Clients/BearerToken/TwilioBearerTokenRestClient.cs

View workflow job for this annotation

GitHub Actions / Test

The type or namespace name 'Script' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)
#endif


Expand Down Expand Up @@ -87,6 +90,21 @@
Edge = edge;
}

/// <summary>
/// Check if an access token is expired or not. Use the System.IdentityModel.Tokens.Jwt; for versions other
/// than net35 and use redirect to custom function if net35
/// </summary>
///
/// <param name="accessToken">access token for which expiry have to be checked</param>
/// <returns>true if expired, false otherwise</returns>
public bool tokenExpired(String accessToken){
#if NET35
return IsTokenExpired(accessToken);
#else
return isTokenExpired(accessToken);
#endif
}

/// <summary>
/// Make a request to the Twilio API
/// </summary>
Expand All @@ -95,12 +113,9 @@
/// <returns>response of the request</returns>
public Response Request(BearerTokenRequest request)
{

if (_accessToken == null ){
//|| isTokenExpired(_accessToken)) {
if ((_accessToken == null )|| tokenExpired(_accessToken)) {
lock (lockObject){
if (_accessToken == null){
//|| isTokenExpired(_accessToken)) {
if ((_accessToken == null) || tokenExpired(_accessToken)) {
_accessToken = _tokenManager.fetchAccessToken();
}
}
Expand Down Expand Up @@ -139,21 +154,78 @@
return ProcessResponse(response);
}

/// <summary>
/// To check if token is expired or not
/// </summary>
///
/// <param name="token">token to validate</param>
/// <returns>True if token is not expired, false otherwise</returns>
// public bool isTokenExpired(String token) {
//
// var tokenHandler = new JwtSecurityTokenHandler();
// var jwtToken = tokenHandler.ReadJwtToken(token);
// var expirationTime = jwtToken.Payload.Exp.HasValue
// ? DateTimeOffset.FromUnixTimeSeconds(jwtToken.Payload.Exp.Value)
// : DateTimeOffset.MinValue;
// return expirationTime <= DateTimeOffset.UtcNow;
// }
#if NET35
public static bool IsTokenExpired(string token)
{
try
{
// Split the token into its components
var parts = token.Split('.');
if (parts.Length != 3)
throw new ArgumentException("Malformed token received");

// Decode the payload (the second part of the JWT)
string payload = Base64UrlEncoder.Decode(parts[1]);

// Parse the payload JSON
var serializer = new JavaScriptSerializer();
var payloadData = serializer.Deserialize<Dictionary<string, object>>(payload);

// Check the 'exp' claim
if (payloadData.TryGetValue("exp", out object expObj))
{
if (long.TryParse(expObj.ToString(), out long exp))
{
DateTime expirationDate = UnixTimeStampToDateTime(exp);
return DateTime.UtcNow > expirationDate;
}
}

// If 'exp' claim is missing or not a valid timestamp, consider the token expired
throw new ApiConnectionException("token expired 1");
return true;
}
catch (Exception ex)
{
// Handle exceptions (e.g., malformed token or invalid JSON)
Console.WriteLine($"Error checking token expiration: {ex.Message}");
throw new ApiConnectionException("token expired 2");
return true; // Consider as expired if there's an error
}
}

private static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
{
// Unix timestamp is seconds past epoch
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddSeconds(unixTimeStamp);
}
#endif

#if !NET35
public bool isTokenExpired(string token){
var handler = new JwtSecurityTokenHandler();
try{
var jwtToken = handler.ReadJwtToken(token);
var exp = jwtToken.Payload.Exp;
if (exp.HasValue)
{
var expirationDate = DateTimeOffset.FromUnixTimeSeconds(exp.Value).UtcDateTime;
return DateTime.UtcNow > expirationDate;
}
else
{
return true; // Assuming token is expired if exp claim is missing
}
}
catch (Exception ex)
{
Console.WriteLine($"Error reading token: {ex.Message}");

return true; // Treat as expired if there is an error
}
}
#endif

#if !NET35
/// <summary>
Expand Down Expand Up @@ -208,6 +280,7 @@
throw new ApiConnectionException("Connection Error: No response received.");
}


if (response.StatusCode >= HttpStatusCode.OK && response.StatusCode < HttpStatusCode.BadRequest)
{
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ private HttpRequestMessage BuildHttpRequest(BearerTokenRequest request)
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authBytes);

httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/scim+json"));
httpRequest.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("utf-8"));

int lastSpaceIndex = PlatVersion.LastIndexOf(" ");
Expand Down
18 changes: 4 additions & 14 deletions src/Twilio/TwilioNoAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
private static string _edge;
private static TwilioNoAuthRestClient _restClient;
private static string _logLevel;
private static ClientProperties clientProperties;

Check warning on line 17 in src/Twilio/TwilioNoAuth.cs

View workflow job for this annotation

GitHub Actions / Test

The field 'TwilioNoAuthClient.clientProperties' is never used

private TwilioNoAuthClient() { }

Expand All @@ -28,27 +29,16 @@
return _restClient;
}

if(_region == null && ClientProperties.region != null) _region = ClientProperties.region;
if(_edge == null && ClientProperties.edge != null) _edge = ClientProperties.edge;
if(_logLevel == null && ClientProperties.logLevel != null) _logLevel = ClientProperties.logLevel;
_restClient = new TwilioNoAuthRestClient(region: _region, edge: _edge)
{
LogLevel = _logLevel
};
return _restClient;
}

/// <summary>
/// Set the logging level
/// </summary>
/// <param name="loglevel">log level</param>
public static void SetLogLevel(string loglevel)
{
if (loglevel != _logLevel)
{
Invalidate();
}

_logLevel = loglevel;
}

/// <summary>
/// Set the rest client
/// </summary>
Expand Down
7 changes: 4 additions & 3 deletions src/Twilio/TwilioOrgsTokenAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
/// </summary>
public class TwilioOrgsTokenAuthClient
{
private static string _accessToken;

Check warning on line 13 in src/Twilio/TwilioOrgsTokenAuth.cs

View workflow job for this annotation

GitHub Actions / Test

The field 'TwilioOrgsTokenAuthClient._accessToken' is never used
private static string _region;
private static string _edge;
private static TwilioBearerTokenRestClient _restClient;
private static string _logLevel;
private static TokenManager _tokenManager;
private static ClientProperties clientProperties;

Check warning on line 19 in src/Twilio/TwilioOrgsTokenAuth.cs

View workflow job for this annotation

GitHub Actions / Test

The field 'TwilioOrgsTokenAuthClient.clientProperties' is never used

private TwilioOrgsTokenAuthClient() { }

Expand Down Expand Up @@ -74,7 +75,7 @@
{
Invalidate();
}

ClientProperties.SetRegion(region);
_region = region;
}

Expand All @@ -88,7 +89,7 @@
{
Invalidate();
}

ClientProperties.SetEdge(edge);
_edge = edge;
}

Expand All @@ -102,7 +103,7 @@
{
Invalidate();
}

ClientProperties.SetLogLevel(_logLevel);
_logLevel = loglevel;
}

Expand Down
Loading