From 4ca2eed70fc8753e196b1bd076db7304ab763a87 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Wed, 16 Oct 2024 15:46:45 +0530 Subject: [PATCH 1/5] Public OAuth uptake for C# libraries --- src/Twilio/Annotations/Deprecated.cs | 15 ++ src/Twilio/AuthStrategies/AuthStrategy.cs | 11 ++ src/Twilio/AuthStrategies/Base64UrlEncode.cs | 32 ++++ .../AuthStrategies/BasicAuthStrategy.cs | 39 +++++ src/Twilio/AuthStrategies/NoAuthStrategy.cs | 17 ++ .../AuthStrategies/TokenAuthStrategy.cs | 147 ++++++++++++++++++ src/Twilio/Clients/TwilioRestClient.cs | 24 ++- .../Credential/ClientCredentialProvider.cs | 63 ++++++++ src/Twilio/Credential/CredentialProvider.cs | 10 ++ .../OrgsClientCredentialProvider.cs | 66 ++++++++ .../Http/BearerToken/ApiTokenManager.cs | 67 ++++++++ .../Http/BearerToken/OrgsTokenManager.cs | 2 +- .../BearerToken/SystemNetTokenHttpClient.cs | 2 +- .../Http/BearerToken/TokenHttpClient.cs | 2 +- src/Twilio/Http/BearerToken/TokenRequest.cs | 2 +- src/Twilio/Http/NoAuth/NoAuthHttpClient.cs | 2 +- src/Twilio/Http/NoAuth/NoAuthRequest.cs | 2 +- .../Http/NoAuth/SystemNetNoAuthHttpClient.cs | 2 +- src/Twilio/Http/Request.cs | 23 ++- src/Twilio/Http/SystemNetHttpClient.cs | 10 +- src/Twilio/Twilio.cs | 122 ++++++++++++++- 21 files changed, 637 insertions(+), 23 deletions(-) create mode 100644 src/Twilio/Annotations/Deprecated.cs create mode 100644 src/Twilio/AuthStrategies/AuthStrategy.cs create mode 100644 src/Twilio/AuthStrategies/Base64UrlEncode.cs create mode 100644 src/Twilio/AuthStrategies/BasicAuthStrategy.cs create mode 100644 src/Twilio/AuthStrategies/NoAuthStrategy.cs create mode 100644 src/Twilio/AuthStrategies/TokenAuthStrategy.cs create mode 100644 src/Twilio/Credential/ClientCredentialProvider.cs create mode 100644 src/Twilio/Credential/CredentialProvider.cs create mode 100644 src/Twilio/Credential/OrgsCredentialProvider/OrgsClientCredentialProvider.cs create mode 100644 src/Twilio/Http/BearerToken/ApiTokenManager.cs diff --git a/src/Twilio/Annotations/Deprecated.cs b/src/Twilio/Annotations/Deprecated.cs new file mode 100644 index 000000000..a1cbd354d --- /dev/null +++ b/src/Twilio/Annotations/Deprecated.cs @@ -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; + } + } +} diff --git a/src/Twilio/AuthStrategies/AuthStrategy.cs b/src/Twilio/AuthStrategies/AuthStrategy.cs new file mode 100644 index 000000000..7f94a0608 --- /dev/null +++ b/src/Twilio/AuthStrategies/AuthStrategy.cs @@ -0,0 +1,11 @@ +namespace Twilio.AuthStrategies +{ + public abstract class AuthStrategy + { + protected AuthStrategy(){} + + public abstract string GetAuthString(); + + public abstract bool RequiresAuthentication(); + } +} \ No newline at end of file diff --git a/src/Twilio/AuthStrategies/Base64UrlEncode.cs b/src/Twilio/AuthStrategies/Base64UrlEncode.cs new file mode 100644 index 000000000..921a86f82 --- /dev/null +++ b/src/Twilio/AuthStrategies/Base64UrlEncode.cs @@ -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 diff --git a/src/Twilio/AuthStrategies/BasicAuthStrategy.cs b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs new file mode 100644 index 000000000..62d6f94b4 --- /dev/null +++ b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs @@ -0,0 +1,39 @@ +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; + } + + } +} diff --git a/src/Twilio/AuthStrategies/NoAuthStrategy.cs b/src/Twilio/AuthStrategies/NoAuthStrategy.cs new file mode 100644 index 000000000..59f60e2e5 --- /dev/null +++ b/src/Twilio/AuthStrategies/NoAuthStrategy.cs @@ -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; + } + } +} diff --git a/src/Twilio/AuthStrategies/TokenAuthStrategy.cs b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs new file mode 100644 index 000000000..4560c663f --- /dev/null +++ b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs @@ -0,0 +1,147 @@ +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 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>(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 + } +} diff --git a/src/Twilio/Clients/TwilioRestClient.cs b/src/Twilio/Clients/TwilioRestClient.cs index 3acc67585..410d2e565 100644 --- a/src/Twilio/Clients/TwilioRestClient.cs +++ b/src/Twilio/Clients/TwilioRestClient.cs @@ -3,6 +3,7 @@ using System.Net; using Newtonsoft.Json; using Twilio.Exceptions; +using Twilio.AuthStrategies; #if !NET35 using System.Threading.Tasks; @@ -51,6 +52,7 @@ public class TwilioRestClient : ITwilioRestClient public string LogLevel { get; set; } = Environment.GetEnvironmentVariable("TWILIO_LOG_LEVEL"); private readonly string _username; private readonly string _password; + private readonly AuthStrategy _authstrategy; /// /// Constructor for a TwilioRestClient @@ -68,11 +70,13 @@ public TwilioRestClient( string accountSid = null, string region = null, HttpClient httpClient = null, - string edge = null + string edge = null, + AuthStrategy authstrategy = null ) { _username = username; _password = password; + _authstrategy = authstrategy; AccountSid = accountSid ?? username; HttpClient = httpClient ?? DefaultClient(); @@ -89,7 +93,13 @@ public TwilioRestClient( /// response of the request public Response Request(Request request) { - request.SetAuth(_username, _password); + + if(_username != null && _password != null){ + request.SetAuth(_username, _password); + } + else if(_authstrategy != null){ + request.SetAuth(_authstrategy); + } if (LogLevel == "debug") LogRequest(request); @@ -102,7 +112,6 @@ public Response Request(Request request) if (UserAgentExtensions != null) request.UserAgentExtensions = UserAgentExtensions; - Response response; try { @@ -116,7 +125,7 @@ public Response Request(Request request) catch (Exception clientException) { throw new ApiConnectionException( - "Connection Error: " + request.Method + request.ConstructUrl(), + "Connection Error1: " + request.Method + request.ConstructUrl(), clientException ); } @@ -132,7 +141,12 @@ public Response Request(Request request) /// Task that resolves to the response of the request public async Task RequestAsync(Request request) { - request.SetAuth(_username, _password); + if(_username != null && _password != null){ + request.SetAuth(_username, _password); + } + else if(_authstrategy != null){ + request.SetAuth(_authstrategy); + } if (Region != null) request.Region = Region; diff --git a/src/Twilio/Credential/ClientCredentialProvider.cs b/src/Twilio/Credential/ClientCredentialProvider.cs new file mode 100644 index 000000000..cec460271 --- /dev/null +++ b/src/Twilio/Credential/ClientCredentialProvider.cs @@ -0,0 +1,63 @@ +using System; +using Twilio.Annotations; +using Twilio.AuthStrategies; +using Twilio.Exceptions; +using Twilio.Http.BearerToken; +using System.Collections.Generic; + +namespace Twilio.Credential +{ + [Beta] + public class ClientCredentialProvider : CredentialProvider + { + private string grantType; + private string clientId; + private string clientSecret; + private TokenManager tokenManager; + + public ClientCredentialProvider(string clientId, string clientSecret) + { + if (clientId == null || clientSecret == null) + { + throw new AuthenticationException("ClientId or ClientSecret cannot be null"); + } + grantType = "client_credentials"; + this.clientId = clientId; + this.clientSecret = clientSecret; + tokenManager = null; + + } + + public ClientCredentialProvider(string clientId, string clientSecret, TokenManager tokenManager) + { + if (clientId == null || clientSecret == null || tokenManager == null) + { + throw new AuthenticationException("ClientId, ClientSecret, or TokenManager cannot be null"); + } + grantType = "client_credentials"; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.tokenManager = tokenManager; + } + + public override AuthStrategy ToAuthStrategy() + { + if (tokenManager == null) + { + tokenManager = new ApiTokenManager(clientId, clientSecret); + } + return new TokenAuthStrategy(tokenManager); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + if (obj == null || GetType() != obj.GetType()) return false; + + var other = (ClientCredentialProvider)obj; + return clientId == other.clientId && + clientSecret == other.clientSecret && + EqualityComparer.Default.Equals(tokenManager, other.tokenManager); + } + } +} diff --git a/src/Twilio/Credential/CredentialProvider.cs b/src/Twilio/Credential/CredentialProvider.cs new file mode 100644 index 000000000..eeae6e501 --- /dev/null +++ b/src/Twilio/Credential/CredentialProvider.cs @@ -0,0 +1,10 @@ +using Twilio.AuthStrategies; + +namespace Twilio.Credential +{ + public abstract class CredentialProvider + { + protected CredentialProvider(){} + public abstract AuthStrategy ToAuthStrategy(); + } +} diff --git a/src/Twilio/Credential/OrgsCredentialProvider/OrgsClientCredentialProvider.cs b/src/Twilio/Credential/OrgsCredentialProvider/OrgsClientCredentialProvider.cs new file mode 100644 index 000000000..3ad4c8944 --- /dev/null +++ b/src/Twilio/Credential/OrgsCredentialProvider/OrgsClientCredentialProvider.cs @@ -0,0 +1,66 @@ +using System; +using Twilio.Annotations; +using Twilio.AuthStrategies; +using Twilio.Exceptions; +using Twilio.Http.BearerToken; +using System.Collections.Generic; + +namespace Twilio.Credential +{ + [Beta] + public class OrgsClientCredentialProvider : CredentialProvider + { + private string grantType; + private string clientId; + private string clientSecret; + private AuthStrategy authStrategy; + private TokenManager tokenManager; + + public OrgsClientCredentialProvider(string clientId, string clientSecret) + { + if (clientId == null || clientSecret == null) + { + throw new AuthenticationException("ClientId or ClientSecret cannot be null"); + } + grantType = "client_credentials"; + this.clientId = clientId; + this.clientSecret = clientSecret; + tokenManager = null; + + } + + public OrgsClientCredentialProvider(string clientId, string clientSecret, TokenManager tokenManager) + { + if (clientId == null || clientSecret == null || tokenManager == null) + { + throw new AuthenticationException("ClientId, ClientSecret, or TokenManager cannot be null"); + } + grantType = "client_credentials"; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.tokenManager = tokenManager; + this.authStrategy = new TokenAuthStrategy(tokenManager); + } + + public override AuthStrategy ToAuthStrategy() + { + if (tokenManager == null) + { + tokenManager = new OrgsTokenManager(clientId, clientSecret); + } + authStrategy = new TokenAuthStrategy(tokenManager); + return authStrategy; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + if (obj == null || GetType() != obj.GetType()) return false; + + var other = (OrgsClientCredentialProvider)obj; + return clientId == other.clientId && + clientSecret == other.clientSecret && + EqualityComparer.Default.Equals(tokenManager, other.tokenManager); + } + } +} diff --git a/src/Twilio/Http/BearerToken/ApiTokenManager.cs b/src/Twilio/Http/BearerToken/ApiTokenManager.cs new file mode 100644 index 000000000..db0f5d9ab --- /dev/null +++ b/src/Twilio/Http/BearerToken/ApiTokenManager.cs @@ -0,0 +1,67 @@ +using Twilio.Rest.PreviewIam.V1; +using Twilio.Exceptions; +using System; +using Twilio.Annotations; + + +namespace Twilio.Http.BearerToken{ + + /// + /// Implementation of a Token Manager + /// + [Beta] + public class ApiTokenManager : TokenManager + { + + public string GrantType { get; } + public string ClientId { get; } + public string ClientSecret { get; set; } + public string Code { get; set; } + public string RedirectUri { get; set; } + public string Audience { get; set; } + public string RefreshToken { get; set; } + public string Scope { get; set; } + + /// Constructor for a ApiTokenManager + public ApiTokenManager( + string clientId, + string clientSecret, + string code = null, + string redirectUri = null, + string audience = null, + string refreshToken = null, + string scope = null + ){ + GrantType = "client_credentials"; + ClientId = clientId; + ClientSecret = clientSecret; + Code = code; + RedirectUri = redirectUri; + Audience = audience; + RefreshToken = refreshToken; + Scope = scope; + } + + public string fetchAccessToken(){ + CreateTokenOptions createTokenOptions = new CreateTokenOptions(GrantType, ClientId); + if(ClientSecret != null){ createTokenOptions.ClientSecret = ClientSecret;} + if(Code != null){ createTokenOptions.Code = Code; } + if(RedirectUri != null){ createTokenOptions.RedirectUri = RedirectUri; } + if(Audience != null){ createTokenOptions.Audience = Audience; } + if(RefreshToken != null){ createTokenOptions.RefreshToken = RefreshToken; } + if(Scope != null){ createTokenOptions.Scope = Scope; } + + TokenResource token; + try{ + token = TokenResource.Create(createTokenOptions); + if(token == null || token.AccessToken == null){ + throw new ApiException("Token creation failed"); + } + }catch(Exception e){ + throw new ApiException("Token creation failed" + e); + } + + return token.AccessToken; + } + } +} \ No newline at end of file diff --git a/src/Twilio/Http/BearerToken/OrgsTokenManager.cs b/src/Twilio/Http/BearerToken/OrgsTokenManager.cs index 0efc7cc66..ee970508d 100644 --- a/src/Twilio/Http/BearerToken/OrgsTokenManager.cs +++ b/src/Twilio/Http/BearerToken/OrgsTokenManager.cs @@ -5,7 +5,7 @@ namespace Twilio.Http.BearerToken{ -/// + /// /// Implementation of a Token Manager /// [Beta] diff --git a/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs b/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs index 18febed18..dfbc84369 100644 --- a/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs +++ b/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs @@ -14,7 +14,7 @@ namespace Twilio.Http.BearerToken /// /// Sample client to make HTTP requests /// - [Beta] + [Deprecated] public class SystemNetTokenHttpClient : TokenHttpClient { #if NET462 diff --git a/src/Twilio/Http/BearerToken/TokenHttpClient.cs b/src/Twilio/Http/BearerToken/TokenHttpClient.cs index 6ccccaa91..45e5ea866 100644 --- a/src/Twilio/Http/BearerToken/TokenHttpClient.cs +++ b/src/Twilio/Http/BearerToken/TokenHttpClient.cs @@ -6,7 +6,7 @@ namespace Twilio.Http.BearerToken /// /// Base http client used to make Twilio requests /// - [Beta] + [Deprecated] public abstract class TokenHttpClient { /// diff --git a/src/Twilio/Http/BearerToken/TokenRequest.cs b/src/Twilio/Http/BearerToken/TokenRequest.cs index 9af9a283d..ca52bfb28 100644 --- a/src/Twilio/Http/BearerToken/TokenRequest.cs +++ b/src/Twilio/Http/BearerToken/TokenRequest.cs @@ -17,7 +17,7 @@ namespace Twilio.Http.BearerToken /// /// Twilio request object with bearer token authentication /// - [Beta] + [Deprecated] public class TokenRequest { private static readonly string DEFAULT_REGION = "us1"; diff --git a/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs b/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs index dfda2fbcb..1430c3102 100644 --- a/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs +++ b/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs @@ -6,7 +6,7 @@ namespace Twilio.Http.NoAuth /// /// Base http client used to make Twilio requests /// - [Beta] + [Deprecated] public abstract class NoAuthHttpClient { /// diff --git a/src/Twilio/Http/NoAuth/NoAuthRequest.cs b/src/Twilio/Http/NoAuth/NoAuthRequest.cs index b50cf6d24..d437ae2f2 100644 --- a/src/Twilio/Http/NoAuth/NoAuthRequest.cs +++ b/src/Twilio/Http/NoAuth/NoAuthRequest.cs @@ -17,7 +17,7 @@ namespace Twilio.Http.NoAuth /// /// Twilio request object with bearer token authentication /// - [Beta] + [Deprecated] public class NoAuthRequest { private static readonly string DEFAULT_REGION = "us1"; diff --git a/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs b/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs index b58cbf8d2..6635bf770 100644 --- a/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs +++ b/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs @@ -14,7 +14,7 @@ namespace Twilio.Http.NoAuth /// /// Sample client to make HTTP requests /// - [Beta] + [Deprecated] public class SystemNetNoAuthHttpClient : NoAuthHttpClient { #if NET462 diff --git a/src/Twilio/Http/Request.cs b/src/Twilio/Http/Request.cs index 869db5e6b..34fa74e14 100644 --- a/src/Twilio/Http/Request.cs +++ b/src/Twilio/Http/Request.cs @@ -4,6 +4,7 @@ using System.Text; using Twilio.Constant; using Twilio.Rest; +using Twilio.AuthStrategies; #if !NET35 using System.Net; @@ -37,6 +38,11 @@ public class Request /// public string Password { get; set; } + /// + /// Auth Strategy + /// + public AuthStrategy AuthStrategy { get; set; } + /// /// Twilio region /// @@ -66,12 +72,12 @@ public class Request /// Header params /// public List> HeaderParams { get; private set; } - + /// /// Content Type /// public EnumConstants.ContentTypeEnum ContentType { get; set; } - + /// /// Body /// @@ -185,6 +191,19 @@ public void SetAuth(string username, string password) { Username = username; Password = password; + AuthStrategy = null; + } + + /// + /// Set auth for the request + /// + /// Auth username + /// Auth password + public void SetAuth(AuthStrategy authStrategy) + { + AuthStrategy = authStrategy; + Username = null; + Password = null; } private static string EncodeParameters(IEnumerable> data) diff --git a/src/Twilio/Http/SystemNetHttpClient.cs b/src/Twilio/Http/SystemNetHttpClient.cs index e8b649e42..6cf90571e 100644 --- a/src/Twilio/Http/SystemNetHttpClient.cs +++ b/src/Twilio/Http/SystemNetHttpClient.cs @@ -94,8 +94,14 @@ private HttpRequestMessage BuildHttpRequest(Request request) request.ConstructUrl() ); - var authBytes = Authentication(request.Username, request.Password); - httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", authBytes); + if(request.Username != null && request.Password != null){ + var authBytes = Authentication(request.Username, request.Password); + httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", authBytes); + } + else if(request.AuthStrategy != null && request.AuthStrategy.RequiresAuthentication()){ + string authHeader = request.AuthStrategy.GetAuthString(); + httpRequest.Headers.Add("Authorization", authHeader); + } httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpRequest.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("utf-8")); diff --git a/src/Twilio/Twilio.cs b/src/Twilio/Twilio.cs index 71e5a605c..b7fa0f4f5 100644 --- a/src/Twilio/Twilio.cs +++ b/src/Twilio/Twilio.cs @@ -1,5 +1,8 @@ using Twilio.Clients; using Twilio.Exceptions; +using Twilio.Credential; +using Twilio.AuthStrategies; +using Twilio.Annotations; namespace Twilio { @@ -10,11 +13,14 @@ public class TwilioClient { private static string _username; private static string _password; + private static AuthStrategy _authstrategy; private static string _accountSid; private static string _region; private static string _edge; private static ITwilioRestClient _restClient; + private static ITwilioRestClient _noAuthRestClient; private static string _logLevel; + private static CredentialProvider _credentialProvider; private TwilioClient() { } @@ -42,6 +48,45 @@ public static void Init(string username, string password, string accountSid) SetAccountSid(accountSid); } + /// + /// Initialize base client with credentialProvider + /// + /// credentialProvider with credential information + public static void Init(CredentialProvider credentialProvider) + { + SetCredentialProvider(credentialProvider); + } + + /// + /// Initialize base client with credentialProvider and account sid + /// + /// credentialProvider with credential information + public static void Init(CredentialProvider credentialProvider, string accountSid) + { + SetCredentialProvider(credentialProvider); + SetAccountSid(accountSid); + } + + /// + /// Set the credential provider + /// + /// credentialProvider with credential information + public static void SetCredentialProvider(CredentialProvider credentialProvider) + { + if (credentialProvider == null) + { + throw new AuthenticationException("Credential Provider can not be null"); + } + + if (credentialProvider != _credentialProvider) + { + Invalidate(); + } + InvalidateBasicCreds(); + + _credentialProvider = credentialProvider; + } + /// /// Set the client username /// @@ -57,6 +102,7 @@ public static void SetUsername(string username) { Invalidate(); } + InvalidateOAuthCreds(); _username = username; } @@ -76,6 +122,7 @@ public static void SetPassword(string password) { Invalidate(); } + InvalidateOAuthCreds(); _password = password; } @@ -108,6 +155,7 @@ public static void SetRegion(string region) if (region != _region) { Invalidate(); + InvalidateNoAuth(); } _region = region; @@ -122,6 +170,7 @@ public static void SetEdge(string edge) if (edge != _edge) { Invalidate(); + InvalidateNoAuth(); } _edge = edge; @@ -136,11 +185,33 @@ public static void SetLogLevel(string loglevel) if (loglevel != _logLevel) { Invalidate(); + InvalidateNoAuth(); } _logLevel = loglevel; } + + /// + /// Get the no auth rest client + /// + /// The no auth rest client + public static ITwilioRestClient GetNoAuthRestClient() + { + if (_noAuthRestClient != null) + { + return _noAuthRestClient; + } + + AuthStrategy noauthstrategy = new NoAuthStrategy(); + _noAuthRestClient = new TwilioRestClient(_username, _password, accountSid: _accountSid, region: _region, edge: _edge, authstrategy: noauthstrategy) + { + LogLevel = _logLevel + }; + + return _noAuthRestClient; + } + /// /// Get the rest client /// @@ -154,15 +225,27 @@ public static ITwilioRestClient GetRestClient() if (_username == null || _password == null) { - throw new AuthenticationException( - "TwilioRestClient was used before AccountSid and AuthToken were set, please call TwilioClient.init()" - ); + if(_credentialProvider == null){ + throw new AuthenticationException( + "Credentials have not been initialized or changed, please call TwilioClient.init()" + ); + } + } + + if(_username != null && _password != null){ + _restClient = new TwilioRestClient(_username, _password, accountSid: _accountSid, region: _region, edge: _edge) + { + LogLevel = _logLevel + }; + } + else if(_credentialProvider != null){ + AuthStrategy authstrategy = _credentialProvider.ToAuthStrategy(); + _restClient = new TwilioRestClient(_username, _password, accountSid: _accountSid, region: _region, edge: _edge, authstrategy: _credentialProvider.ToAuthStrategy()) + { + LogLevel = _logLevel + }; } - _restClient = new TwilioRestClient(_username, _password, accountSid: _accountSid, region: _region, edge: _edge) - { - LogLevel = _logLevel - }; return _restClient; } @@ -183,6 +266,31 @@ public static void Invalidate() _restClient = null; } + /// + /// Clear out the No Auth Rest Client + /// + public static void InvalidateNoAuth() + { + _noAuthRestClient = null; + } + + /// + /// Clear out the credential provider + /// + public static void InvalidateOAuthCreds() + { + _credentialProvider = null; + } + + /// + /// Clear out the basic credentials username and password + /// + public static void InvalidateBasicCreds() + { + _username = null; + _password = null; + } + /// /// Test if your environment is impacted by a TLS or certificate change /// by sending an HTTP request to the test endpoint tls-test.twilio.com:443 From 3486d1a7f430dc823bd0ff1cc6a8167d10ffdee0 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Wed, 16 Oct 2024 16:01:14 +0530 Subject: [PATCH 2/5] Public OAuth uptake for C# libraries --- src/Twilio/AuthStrategies/BasicAuthStrategy.cs | 5 +++++ src/Twilio/AuthStrategies/TokenAuthStrategy.cs | 5 +++++ src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs | 2 +- src/Twilio/Clients/TwilioRestClient.cs | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Twilio/AuthStrategies/BasicAuthStrategy.cs b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs index 62d6f94b4..0ab73b9a8 100644 --- a/src/Twilio/AuthStrategies/BasicAuthStrategy.cs +++ b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs @@ -35,5 +35,10 @@ public override bool Equals(object obj) return username == that.username && password == that.password; } + public override int GetHashCode() + { + return HashCode.Combine(username, password); + } + } } diff --git a/src/Twilio/AuthStrategies/TokenAuthStrategy.cs b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs index 4560c663f..a0fd07e60 100644 --- a/src/Twilio/AuthStrategies/TokenAuthStrategy.cs +++ b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs @@ -62,6 +62,11 @@ public override bool Equals(object obj) return token == that.token && tokenManager.Equals(that.tokenManager); } + public override int GetHashCode() + { + return HashCode.Combine(token, tokenManager); + } + public bool tokenExpired(String accessToken){ #if NET35 diff --git a/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs b/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs index 6302b0c7a..6ac86fcf4 100644 --- a/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs +++ b/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs @@ -28,7 +28,7 @@ namespace Twilio.Clients.BearerToken /// /// Implementation of a TwilioRestClient. /// - [Beta] + [Deprecated] public class TwilioOrgsTokenRestClient { /// diff --git a/src/Twilio/Clients/TwilioRestClient.cs b/src/Twilio/Clients/TwilioRestClient.cs index 410d2e565..6f4d4d62e 100644 --- a/src/Twilio/Clients/TwilioRestClient.cs +++ b/src/Twilio/Clients/TwilioRestClient.cs @@ -125,7 +125,7 @@ public Response Request(Request request) catch (Exception clientException) { throw new ApiConnectionException( - "Connection Error1: " + request.Method + request.ConstructUrl(), + "Connection Error: " + request.Method + request.ConstructUrl(), clientException ); } From 0ec6f953e5e98cedc05f564235da7986b4216351 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Wed, 16 Oct 2024 18:26:10 +0530 Subject: [PATCH 3/5] Public OAuth uptake for C# libraries --- src/Twilio/Credential/ClientCredentialProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Twilio/Credential/ClientCredentialProvider.cs b/src/Twilio/Credential/ClientCredentialProvider.cs index cec460271..d822763ca 100644 --- a/src/Twilio/Credential/ClientCredentialProvider.cs +++ b/src/Twilio/Credential/ClientCredentialProvider.cs @@ -21,7 +21,7 @@ public ClientCredentialProvider(string clientId, string clientSecret) { throw new AuthenticationException("ClientId or ClientSecret cannot be null"); } - grantType = "client_credentials"; + this.grantType = "client_credentials"; this.clientId = clientId; this.clientSecret = clientSecret; tokenManager = null; From e38ff144d49d2be4bef8d30dd280c9e62ddf609f Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Wed, 23 Oct 2024 11:08:02 +0530 Subject: [PATCH 4/5] Improving the hash code function --- src/Twilio/AuthStrategies/BasicAuthStrategy.cs | 5 ++++- src/Twilio/AuthStrategies/TokenAuthStrategy.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Twilio/AuthStrategies/BasicAuthStrategy.cs b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs index 0ab73b9a8..9557d9a29 100644 --- a/src/Twilio/AuthStrategies/BasicAuthStrategy.cs +++ b/src/Twilio/AuthStrategies/BasicAuthStrategy.cs @@ -37,7 +37,10 @@ public override bool Equals(object obj) public override int GetHashCode() { - return HashCode.Combine(username, password); + int hash = 17; + hash = hash * 31 + (username != null ? username.GetHashCode() : 0); + hash = hash * 31 + (password != null ? password.GetHashCode() : 0); + return hash; } } diff --git a/src/Twilio/AuthStrategies/TokenAuthStrategy.cs b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs index a0fd07e60..68ad69f56 100644 --- a/src/Twilio/AuthStrategies/TokenAuthStrategy.cs +++ b/src/Twilio/AuthStrategies/TokenAuthStrategy.cs @@ -64,7 +64,10 @@ public override bool Equals(object obj) public override int GetHashCode() { - return HashCode.Combine(token, tokenManager); + int hash = 17; + hash = hash * 31 + (token != null ? token.GetHashCode() : 0); + hash = hash * 31 + (tokenManager != null ? tokenManager.GetHashCode() : 0); + return hash; } From 9e721d2bae45da88cca5edd059a770f3dbd4f110 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Wed, 23 Oct 2024 12:29:56 +0530 Subject: [PATCH 5/5] Modifying cluster tests --- test/Twilio.Test/ClusterTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Twilio.Test/ClusterTest.cs b/test/Twilio.Test/ClusterTest.cs index d4b2e53ee..2726df927 100644 --- a/test/Twilio.Test/ClusterTest.cs +++ b/test/Twilio.Test/ClusterTest.cs @@ -135,7 +135,7 @@ public void TestListParams() [Category("ClusterTest")] public void TestFetchingOrgsAccounts() { - Twilio.Base.BearerToken.TokenResourceSet accountList = null; + Twilio.Base.ResourceSet accountList = null; accountList = Twilio.Rest.PreviewIam.Organizations.AccountResource.Read(orgsSid); Assert.IsNotNull(accountList.ElementAt(0).FriendlyName);