-
Notifications
You must be signed in to change notification settings - Fork 303
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
feat: Public OAuth uptake for C# libraries #762
Changes from 2 commits
4ca2eed
3486d1a
0ec6f95
e38ff14
006d4db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System; | ||
|
||
namespace Twilio.Annotations | ||
{ | ||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] | ||
public class Deprecated : Attribute | ||
{ | ||
public string Message { get; } | ||
|
||
public Deprecated(string message = "This feature is deprecated") | ||
{ | ||
Message = message; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
namespace Twilio.AuthStrategies | ||
{ | ||
public abstract class AuthStrategy | ||
{ | ||
protected AuthStrategy(){} | ||
|
||
public abstract string GetAuthString(); | ||
|
||
public abstract bool RequiresAuthentication(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#if NET35 | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Web.Script.Serialization; | ||
using Twilio.Annotations; | ||
|
||
namespace Twilio.AuthStrategies{ | ||
|
||
[Beta] | ||
public abstract class Base64UrlEncode | ||
{ | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using System; | ||
using System.Text; | ||
|
||
namespace Twilio.AuthStrategies | ||
{ | ||
public class BasicAuthStrategy : AuthStrategy | ||
{ | ||
private string username; | ||
private string password; | ||
|
||
public BasicAuthStrategy(string username, string password) | ||
{ | ||
this.username = username; | ||
this.password = password; | ||
} | ||
|
||
public override string GetAuthString() | ||
{ | ||
var credentials = username + ":" + password; | ||
var encoded = System.Text.Encoding.UTF8.GetBytes(credentials); | ||
var finalEncoded = Convert.ToBase64String(encoded); | ||
return $"Basic {finalEncoded}"; | ||
} | ||
|
||
public override bool RequiresAuthentication() | ||
{ | ||
return true; | ||
} | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
if (ReferenceEquals(this, obj)) return true; | ||
if (obj == null || GetType() != obj.GetType()) return false; | ||
var that = (BasicAuthStrategy)obj; | ||
return username == that.username && password == that.password; | ||
} | ||
|
||
public override int GetHashCode() | ||
{ | ||
return HashCode.Combine(username, password); | ||
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
Check failure on line 40 in src/Twilio/AuthStrategies/BasicAuthStrategy.cs GitHub Actions / Test
|
||
} | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Twilio.AuthStrategies | ||
{ | ||
public class NoAuthStrategy : AuthStrategy | ||
{ | ||
public NoAuthStrategy(){} | ||
|
||
public override string GetAuthString() | ||
{ | ||
return string.Empty; | ||
} | ||
|
||
public override bool RequiresAuthentication() | ||
{ | ||
return false; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
using System; | ||
using System.Threading; | ||
using Twilio.Http.BearerToken; | ||
using Twilio.Exceptions; | ||
|
||
#if !NET35 | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Threading.Tasks; | ||
#endif | ||
|
||
#if NET35 | ||
using Twilio.Http.Net35; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using System.Web.Script.Serialization; | ||
#endif | ||
|
||
namespace Twilio.AuthStrategies | ||
{ | ||
public class TokenAuthStrategy : AuthStrategy | ||
{ | ||
private string token; | ||
private TokenManager tokenManager; | ||
|
||
|
||
public TokenAuthStrategy(TokenManager tokenManager) | ||
{ | ||
this.tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); | ||
} | ||
|
||
public override string GetAuthString() | ||
{ | ||
FetchToken(); | ||
return $"Bearer {token}"; | ||
} | ||
|
||
public override bool RequiresAuthentication() | ||
{ | ||
return true; | ||
} | ||
|
||
// Token-specific refresh logic | ||
private void FetchToken() | ||
{ | ||
if (string.IsNullOrEmpty(token) || tokenExpired(token)) | ||
{ | ||
lock (typeof(TokenAuthStrategy)) | ||
{ | ||
if (string.IsNullOrEmpty(token) || tokenExpired(token)) | ||
{ | ||
token = tokenManager.fetchAccessToken(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
if (ReferenceEquals(this, obj)) return true; | ||
if (obj == null || GetType() != obj.GetType()) return false; | ||
var that = (TokenAuthStrategy)obj; | ||
return token == that.token && tokenManager.Equals(that.tokenManager); | ||
} | ||
|
||
public override int GetHashCode() | ||
{ | ||
return HashCode.Combine(token, tokenManager); | ||
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
Check failure on line 67 in src/Twilio/AuthStrategies/TokenAuthStrategy.cs GitHub Actions / Test
|
||
} | ||
|
||
|
||
public bool tokenExpired(String accessToken){ | ||
#if NET35 | ||
return IsTokenExpired(accessToken); | ||
#else | ||
return isTokenExpired(accessToken); | ||
#endif | ||
} | ||
|
||
#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 = Base64UrlEncode.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"); | ||
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"); | ||
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 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any function that works in both old and new framework? instead of making two different functions, if we can use single one to do it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we cannot use single function to do it, whatever works in 3.5 is deprecated in later versions and vice versa