-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Public OAuth uptake for C# libraries (#762)
* Public OAuth uptake for C# libraries
- Loading branch information
Showing
22 changed files
with
653 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
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() | ||
{ | ||
int hash = 17; | ||
hash = hash * 31 + (username != null ? username.GetHashCode() : 0); | ||
hash = hash * 31 + (password != null ? password.GetHashCode() : 0); | ||
return hash; | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
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() | ||
{ | ||
int hash = 17; | ||
hash = hash * 31 + (token != null ? token.GetHashCode() : 0); | ||
hash = hash * 31 + (tokenManager != null ? tokenManager.GetHashCode() : 0); | ||
return hash; | ||
} | ||
|
||
|
||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.