diff --git a/README.md b/README.md index 07f5306ec..ea98de730 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ var message = MessageResource.Create( Console.WriteLine(message.Sid); ``` +Examples on how to make rest calls with bearer token authentication is added [here](https://github.com/twilio/twilio-csharp/blob/orgs_api_uptake/examples/BearerTokenAuthentication.md) + ## Specify Region and/or Edge To take advantage of Twilio's [Global Infrastructure](https://www.twilio.com/docs/global-infrastructure), specify the target Region and/or Edge for the client: @@ -82,7 +84,7 @@ TwilioClient.SetRegion("au1"); TwilioClient.SetEdge("sydney"); ``` -This will result in the `hostname` transforming from `api.twilio.com` to `api.sydney.au1.twilio.com`. +This will result in the `hostname` transforming from `api.twilio.com` to `api.sydney.au1.twilio.com`. Use appropriate client depending on the type of authentication used ## Enable debug logging diff --git a/examples/BearerTokenAuthentication.md b/examples/BearerTokenAuthentication.md new file mode 100644 index 000000000..29dd6952a --- /dev/null +++ b/examples/BearerTokenAuthentication.md @@ -0,0 +1,23 @@ +```csharp +using System; +using Twilio; +using Twilio.Base; +using Twilio.Exceptions; +using Twilio.Rest.Api.V2010.Account; +using Twilio.Rest.PreviewIam.Organizations; + +//Find client id, client secret and organisation sid from admin center of your organisation +//path account sid is the sid of the account withing the organisation +class Program +{ + static void Main(string[] args) + { + TwilioOrgsTokenAuthClient.Init(GRANT_TYPE, CLIENT_ID, CLIENT_SECRET); + Twilio.Base.BearerToken.TokenResourceSet accountList = null; + accountList = Twilio.Rest.PreviewIam.Organizations.AccountResource.Read(pathOrganizationSid: ORGS_SID); + Console.WriteLine(accountList.ElementAt(0).FriendlyName); + var account = Twilio.Rest.PreviewIam.Organizations.AccountResource.Fetch(pathOrganizationSid: ORGS_SID, pathAccountSid: PATH_ACCOUNT_SID); + Console.WriteLine(account.FriendlyName); + } +} +``` diff --git a/src/Twilio/Annotations/Beta.cs b/src/Twilio/Annotations/Beta.cs new file mode 100644 index 000000000..9a97aeb66 --- /dev/null +++ b/src/Twilio/Annotations/Beta.cs @@ -0,0 +1,15 @@ +using System; + +namespace Twilio.Annotations +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class Beta : Attribute + { + public string Message { get; } + + public Beta(string message = "This feature is in beta and may change in future versions.") + { + Message = message; + } + } +} diff --git a/src/Twilio/Annotations/Preview.cs b/src/Twilio/Annotations/Preview.cs new file mode 100644 index 000000000..3b6aa1470 --- /dev/null +++ b/src/Twilio/Annotations/Preview.cs @@ -0,0 +1,15 @@ +using System; + +namespace Twilio.Annotations +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] + public sealed class Preview : Attribute + { + public string Value { get; } + + public Preview(string value = "This class/method is under preview and is subject to change. Use with caution.") + { + Value = value; + } + } +} diff --git a/src/Twilio/Base/BearerToken/TokenResourceSet.cs b/src/Twilio/Base/BearerToken/TokenResourceSet.cs new file mode 100644 index 000000000..37b032d2d --- /dev/null +++ b/src/Twilio/Base/BearerToken/TokenResourceSet.cs @@ -0,0 +1,122 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using Twilio.Clients; +using Twilio.Clients.BearerToken; + +namespace Twilio.Base.BearerToken +{ + /// + /// A collection of resources of type T + /// + /// + /// Resource Type + public class TokenResourceSet : IEnumerable where T : Resource + { + /// + /// Automatically iterate through pages of results + /// + public bool AutoPaging { get; set; } + + private readonly TwilioOrgsTokenRestClient _client; + private readonly ReadOptions _options; + private readonly long _pageLimit; + + private long _pages; + private long _processed; + private Page _page; + private IEnumerator _iterator; + + /// + /// Create a new resource set + /// + /// + /// Page of resources + /// Read options + /// Client to make requests + public TokenResourceSet(Page page, ReadOptions options, TwilioOrgsTokenRestClient client) + { + _page = page; + _options = options; + _client = client; + + _iterator = page.Records.GetEnumerator(); + _processed = 0; + _pages = 1; + _pageLimit = long.MaxValue; + + AutoPaging = true; + + if (_options.Limit != null) + { + _pageLimit = (long) (Math.Ceiling((double) _options.Limit.Value / page.PageSize)); + } + } + + /// + /// Get iterator for resources + /// + /// + /// IEnumerator of resources + public IEnumerator GetEnumerator() + { + while (_page != null) + { + _iterator.Reset(); + while (_iterator.MoveNext()) + { + // Exit if we've reached item limit + if (_options.Limit != null && _processed > _options.Limit.Value) + { + yield break; + } + + _processed++; + yield return _iterator.Current; + } + + if (AutoPaging && _page.HasNextPage()) + { + FetchNextPage(); + } + else + { + break; + } + } + } + + /// + /// Get iterator for resources + /// + /// + /// IEnumerator of resources + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private void FetchNextPage() + { + if (!_page.HasNextPage() || _pages >= _pageLimit) + { + _page = null; + _iterator = null; + return; + } + + _pages++; + _page = (Page)GetNextPage().Invoke(null, new object[]{ _page, _client }); + _iterator = _page.Records.GetEnumerator(); + } + + private static MethodInfo GetNextPage() + { +#if !NET35 + return typeof(T).GetRuntimeMethod("NextPage", new[]{ typeof(Page), typeof(TwilioOrgsTokenRestClient) }); +#else + return typeof(T).GetMethod("NextPage", new[]{ typeof(Page), typeof(TwilioOrgsTokenRestClient) }); +#endif + } + } +} diff --git a/src/Twilio/Base/Page.cs b/src/Twilio/Base/Page.cs index cf1a72234..50b95e66e 100644 --- a/src/Twilio/Base/Page.cs +++ b/src/Twilio/Base/Page.cs @@ -125,36 +125,43 @@ public static Page FromJson(string recordKey, string json) var parsedRecords = records.Children().Select( record => JsonConvert.DeserializeObject(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( + parsedRecords, + root.TryGetValue("page_size", out pageSize) ? root["page_size"].Value() : parsedRecords.Count, + uri: uriNode.Value(), + firstPageUri: root.TryGetValue("first_page_uri", out firstPageUri) ? root["first_page_uri"].Value() : null, + nextPageUri: root.TryGetValue("next_page_uri", out nextPageUri) ? root["next_page_uri"].Value() : null, + previousPageUri: root.TryGetValue("previous_page_uri", out previousPageUri) ? root["previous_page_uri"].Value() : null + ); + } + } - // v2010 API + // next-gen API + if(root["meta"] != null){ + var meta = root["meta"]; return new Page( parsedRecords, - root.TryGetValue("page_size", out pageSize) ? root["page_size"].Value() : parsedRecords.Count, - uri: uriNode.Value(), - firstPageUri: root.TryGetValue("first_page_uri", out firstPageUri) ? root["first_page_uri"].Value() : null, - nextPageUri: root.TryGetValue("next_page_uri", out nextPageUri) ? root["next_page_uri"].Value() : null, - previousPageUri: root.TryGetValue("previous_page_uri", out previousPageUri) ? root["previous_page_uri"].Value() : null + meta["page_size"].Value(), + url: meta["url"].Value(), + firstPageUrl: meta["first_page_url"].Value(), + nextPageUrl: meta["next_page_url"].Value(), + previousPageUrl: meta["previous_page_url"].Value() ); } - // next-gen API - var meta = root["meta"]; - return new Page( - parsedRecords, - meta["page_size"].Value(), - url: meta["url"].Value(), - firstPageUrl: meta["first_page_url"].Value(), - nextPageUrl: meta["next_page_url"].Value(), - previousPageUrl: meta["previous_page_url"].Value() - ); + return new Page(parsedRecords, 0, null, null, null, null); + } } } diff --git a/src/Twilio/Clients/Base64UrlEncoder.cs b/src/Twilio/Clients/Base64UrlEncoder.cs new file mode 100644 index 000000000..a47662072 --- /dev/null +++ b/src/Twilio/Clients/Base64UrlEncoder.cs @@ -0,0 +1,30 @@ +#if NET35 +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Script.Serialization; + +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 diff --git a/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs b/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs new file mode 100644 index 000000000..73247aa12 --- /dev/null +++ b/src/Twilio/Clients/BearerToken/TwilioOrgsTokenRestClient.cs @@ -0,0 +1,390 @@ +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; +#if NET35 +using Twilio.Http.Net35; +using System.Collections.Generic; +using System.Text; +using System.Web.Script.Serialization; +#endif + + +namespace Twilio.Clients.BearerToken +{ + /// + /// Implementation of a TwilioRestClient. + /// + public class TwilioOrgsTokenRestClient + { + /// + /// Client to make HTTP requests + /// + public TokenHttpClient HttpClient { get; } + + /// + /// Twilio region to make requests to + /// + public string Region { get; } + + /// + /// Twilio edge to make requests to + /// + public string Edge { get; set; } + + /// + /// Additions to the user agent string + /// + public string[] UserAgentExtensions { get; set; } + + /// + /// Log level for logging + /// + public string LogLevel { get; set; } = Environment.GetEnvironmentVariable("TWILIO_LOG_LEVEL"); + + /// + /// Token Manage for managing and refreshing tokens + /// + private TokenManager _tokenManager { get; set; } + + /// + /// Access token used for rest calls with bearer token authentication method + /// + private string _accessToken; + + private readonly object lockObject = new object(); + + /// + /// Constructor for a TwilioRestClient + /// + /// + /// to manage access token for requests + /// account sid to make requests for + /// region to make requests for + /// http client used to make the requests + /// edge to make requests for + public TwilioOrgsTokenRestClient( + TokenManager tokenManager, + string region = null, + TokenHttpClient httpClient = null, + string edge = null + ) + { + _tokenManager = tokenManager; + + HttpClient = httpClient ?? DefaultClient(); + + Region = region; + Edge = edge; + } + + /// + /// 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 + /// + /// + /// access token for which expiry have to be checked + /// true if expired, false otherwise + public bool tokenExpired(String accessToken){ + #if NET35 + return IsTokenExpired(accessToken); + #else + return isTokenExpired(accessToken); + #endif + } + + /// + /// Make a request to the Twilio API + /// + /// + /// request to make + /// response of the request + public Response Request(TokenRequest request) + { + if ((_accessToken == null )|| tokenExpired(_accessToken)) { + lock (lockObject){ + if ((_accessToken == null) || tokenExpired(_accessToken)) { + _accessToken = _tokenManager.fetchAccessToken(); + } + } + } + request.SetAuth(_accessToken); + + if (LogLevel == "debug") + LogRequest(request); + + if (Region != null) + request.Region = Region; + + if (Edge != null) + request.Edge = Edge; + + if (UserAgentExtensions != null) + request.UserAgentExtensions = UserAgentExtensions; + + Response response; + try + { + response = HttpClient.MakeRequest(request); + if (LogLevel == "debug") + { + Console.WriteLine("response.status: " + response.StatusCode); + Console.WriteLine("response.headers: " + response.Headers); + } + } + catch (Exception clientException) + { + throw new ApiConnectionException( + "Connection Error: " + request.Method + request.ConstructUrl(), + clientException + ); + } + return ProcessResponse(response); + } + +#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>(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 + /// + /// Make a request to the Twilio API + /// + /// + /// request to make + /// Task that resolves to the response of the request + public async Task RequestAsync(TokenRequest request) + { + request.SetAuth(_accessToken); + + if (Region != null) + request.Region = Region; + + if (Edge != null) + request.Edge = Edge; + + if (UserAgentExtensions != null) + request.UserAgentExtensions = UserAgentExtensions; + + Response response; + try + { + response = await HttpClient.MakeRequestAsync(request); + } + catch (Exception clientException) + { + throw new ApiConnectionException( + "Connection Error: " + request.Method + request.ConstructUrl(), + clientException + ); + } + return ProcessResponse(response); + } + + private static TokenHttpClient DefaultClient() + { + return new SystemNetTokenHttpClient(); + } +#else + private static TokenHttpClient DefaultClient() + { + return new WebBearerTokenRequestClient(); + } +#endif + + private static Response ProcessResponse(Response response) + { + if (response == null) + { + throw new ApiConnectionException("Connection Error: No response received."); + } + + + if (response.StatusCode >= HttpStatusCode.OK && response.StatusCode < HttpStatusCode.BadRequest) + { + return response; + } + + // Deserialize and throw exception + RestException restException = null; + try + { + restException = RestException.FromJson(response.Content); + } + catch (JsonReaderException) { /* Allow null check below to handle */ } + + if (restException == null) + { + throw new ApiException("Api Error: " + response.StatusCode + " - " + (response.Content ?? "[no content]")); + } + + throw new ApiException( + restException.Code, + (int)response.StatusCode, + restException.Message ?? "Unable to make request, " + response.StatusCode, + restException.MoreInfo, + restException.Details + ); + } + + /// + /// 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 + /// It's a bit easier to call this method from TwilioClient.ValidateSslCertificate(). + /// + public static void ValidateSslCertificate() + { + ValidateSslCertificate(DefaultClient()); + } + + /// + /// Test that this application can use updated SSL certificates on + /// tls-test.twilio.com:443. Generally, you'll want to use the version of this + /// function that takes no parameters unless you have a reason not to. + /// + /// + /// HTTP Client to use for testing the request + public static void ValidateSslCertificate(TokenHttpClient client) + { + TokenRequest request = new TokenRequest("GET", "tls-test", ":443/", null); + + try + { + Response response = client.MakeRequest(request); + + if (!response.StatusCode.Equals(HttpStatusCode.OK)) + { + throw new CertificateValidationException( + "Unexpected response from certificate endpoint", + null, + response + ); + } + } + catch (CertificateValidationException e) + { + throw e; + } + catch (Exception e) + { + throw new CertificateValidationException( + "Connection to tls-test.twilio.com:443 failed", + e, + null + ); + } + } + + /// + /// Format request information when LogLevel is set to debug + /// + /// + /// HTTP request + private static void LogRequest(TokenRequest request) + { + Console.WriteLine("-- BEGIN Twilio API Request --"); + Console.WriteLine("request.method: " + request.Method); + Console.WriteLine("request.URI: " + request.Uri); + + if (request.QueryParams != null) + { + request.QueryParams.ForEach(parameter => Console.WriteLine(parameter.Key + ":" + parameter.Value)); + } + + if (request.HeaderParams != null) + { + for (int i = 0; i < request.HeaderParams.Count; i++) + { + var lowercaseHeader = request.HeaderParams[i].Key.ToLower(); + if (lowercaseHeader.Contains("authorization") == false) + { + Console.WriteLine(request.HeaderParams[i].Key + ":" + request.HeaderParams[i].Value); + } + } + } + + Console.WriteLine("-- END Twilio API Request --"); + } + } +} diff --git a/src/Twilio/Clients/NoAuth/TwilioNoAuthRestClient.cs b/src/Twilio/Clients/NoAuth/TwilioNoAuthRestClient.cs new file mode 100644 index 000000000..fb8c7f951 --- /dev/null +++ b/src/Twilio/Clients/NoAuth/TwilioNoAuthRestClient.cs @@ -0,0 +1,273 @@ +using System; +using System.Net; +using System.Linq; +using Newtonsoft.Json; +using Twilio.Exceptions; +using Twilio.Http.BearerToken; +using Twilio.Jwt; + + +#if !NET35 +using System.Threading.Tasks; +#endif + +using Twilio.Http; +using Twilio.Http.NoAuth; +#if NET35 +using Twilio.Http.Net35; +#endif + + +namespace Twilio.Clients.NoAuth +{ + /// + /// Implementation of a TwilioRestClient. + /// + public class TwilioNoAuthRestClient + { + /// + /// Client to make HTTP requests + /// + public NoAuthHttpClient HttpClient { get; } + + /// + /// Twilio region to make requests to + /// + public string Region { get; } + + /// + /// Twilio edge to make requests to + /// + public string Edge { get; set; } + + /// + /// Additions to the user agent string + /// + public string[] UserAgentExtensions { get; set; } + + /// + /// Log level for logging + /// + public string LogLevel { get; set; } = Environment.GetEnvironmentVariable("TWILIO_LOG_LEVEL"); + + /// + /// Constructor for a TwilioRestClient + /// + /// + /// Region to make requests for + /// HTTP client used to make the requests + /// Edge to make requests for + public TwilioNoAuthRestClient( + string region = null, + NoAuthHttpClient httpClient = null, + string edge = null + ) + { + + HttpClient = httpClient ?? DefaultClient(); + + Region = region; + Edge = edge; + } + + /// + /// Make a request to the Twilio API + /// + /// + /// request to make + /// response of the request + public Response Request(NoAuthRequest request) + { + + if (LogLevel == "debug") + LogRequest(request); + + if (Region != null) + request.Region = Region; + + if (Edge != null) + request.Edge = Edge; + + if (UserAgentExtensions != null) + request.UserAgentExtensions = UserAgentExtensions; + + Response response; + try + { + response = HttpClient.MakeRequest(request); + if (LogLevel == "debug") + { + Console.WriteLine("response.status: " + response.StatusCode); + Console.WriteLine("response.headers: " + response.Headers); + } + } + catch (Exception clientException) + { + throw new ApiConnectionException( + "Connection Error: " + request.Method + request.ConstructUrl(), + clientException + ); + } + return ProcessResponse(response); + } + + +#if !NET35 + /// + /// Make a request to the Twilio API + /// + /// + /// request to make + /// Task that resolves to the response of the request + public async Task RequestAsync(NoAuthRequest request) + { + + if (Region != null) + request.Region = Region; + + if (Edge != null) + request.Edge = Edge; + + if (UserAgentExtensions != null) + request.UserAgentExtensions = UserAgentExtensions; + + Response response; + try + { + response = await HttpClient.MakeRequestAsync(request); + } + catch (Exception clientException) + { + throw new ApiConnectionException( + "Connection Error: " + request.Method + request.ConstructUrl(), + clientException + ); + } + return ProcessResponse(response); + } + + private static NoAuthHttpClient DefaultClient() + { + return new SystemNetNoAuthHttpClient(); + } +#else + private static NoAuthHttpClient DefaultClient() + { + return new WebNoAuthRequestClient(); + } +#endif + + private static Response ProcessResponse(Response response) + { + if (response == null) + { + throw new ApiConnectionException("Connection Error: No response received."); + } + + if (response.StatusCode >= HttpStatusCode.OK && response.StatusCode < HttpStatusCode.BadRequest) + { + return response; + } + + // Deserialize and throw exception + RestException restException = null; + try + { + restException = RestException.FromJson(response.Content); + } + catch (JsonReaderException) { /* Allow null check below to handle */ } + + if (restException == null) + { + throw new ApiException("Api Error: " + response.StatusCode + " - " + (response.Content ?? "[no content]")); + } + + throw new ApiException( + restException.Code, + (int)response.StatusCode, + restException.Message ?? "Unable to make request, " + response.StatusCode, + restException.MoreInfo, + restException.Details + ); + } + + /// + /// 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 + /// It's a bit easier to call this method from TwilioClient.ValidateSslCertificate(). + /// + public static void ValidateSslCertificate() + { + ValidateSslCertificate(DefaultClient()); + } + + /// + /// Test that this application can use updated SSL certificates on + /// tls-test.twilio.com:443. Generally, you'll want to use the version of this + /// function that takes no parameters unless you have a reason not to. + /// + /// + /// HTTP Client to use for testing the request + public static void ValidateSslCertificate(NoAuthHttpClient client) + { + NoAuthRequest request = new NoAuthRequest("GET", "tls-test", ":443/", null); + + try + { + Response response = client.MakeRequest(request); + + if (!response.StatusCode.Equals(HttpStatusCode.OK)) + { + throw new CertificateValidationException( + "Unexpected response from certificate endpoint", + null, + response + ); + } + } + catch (CertificateValidationException e) + { + throw e; + } + catch (Exception e) + { + throw new CertificateValidationException( + "Connection to tls-test.twilio.com:443 failed", + e, + null + ); + } + } + + /// + /// Format request information when LogLevel is set to debug + /// + /// + /// HTTP request + private static void LogRequest(NoAuthRequest request) + { + Console.WriteLine("-- BEGIN Twilio API Request --"); + Console.WriteLine("request.method: " + request.Method); + Console.WriteLine("request.URI: " + request.Uri); + + if (request.QueryParams != null) + { + request.QueryParams.ForEach(parameter => Console.WriteLine(parameter.Key + ":" + parameter.Value)); + } + + if (request.HeaderParams != null) + { + for (int i = 0; i < request.HeaderParams.Count; i++) + { + var lowercaseHeader = request.HeaderParams[i].Key.ToLower(); + if (lowercaseHeader.Contains("authorization") == false) + { + Console.WriteLine(request.HeaderParams[i].Key + ":" + request.HeaderParams[i].Value); + } + } + } + + Console.WriteLine("-- END Twilio API Request --"); + } + } +} diff --git a/src/Twilio/Constant/EnumConstants.cs b/src/Twilio/Constant/EnumConstants.cs index 9a9332e28..8be6a71f1 100644 --- a/src/Twilio/Constant/EnumConstants.cs +++ b/src/Twilio/Constant/EnumConstants.cs @@ -11,6 +11,7 @@ public sealed class ContentTypeEnum : StringEnum { private ContentTypeEnum(string value) : base(value) {} public static readonly ContentTypeEnum JSON = new ContentTypeEnum("application/json"); + public static readonly ContentTypeEnum SCIM = new ContentTypeEnum("application/scim"); public static readonly ContentTypeEnum FORM_URLENCODED = new ContentTypeEnum("application/x-www-form-urlencoded"); } } diff --git a/src/Twilio/Http/BearerToken/OrgsTokenManager.cs b/src/Twilio/Http/BearerToken/OrgsTokenManager.cs new file mode 100644 index 000000000..80635aae3 --- /dev/null +++ b/src/Twilio/Http/BearerToken/OrgsTokenManager.cs @@ -0,0 +1,64 @@ +using Twilio.Rest.PreviewIam.V1; +using Twilio.Exceptions; +using System; + +namespace Twilio.Http.BearerToken{ + +/// + /// Implementation of a Token Manager + /// + public class OrgsTokenManager : 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 OrgsTokenManager + public OrgsTokenManager( + 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/SystemNetTokenHttpClient.cs b/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs new file mode 100644 index 000000000..63dc28100 --- /dev/null +++ b/src/Twilio/Http/BearerToken/SystemNetTokenHttpClient.cs @@ -0,0 +1,143 @@ +#if !NET35 +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Text; +using Twilio.Constant; + +namespace Twilio.Http.BearerToken +{ + /// + /// Sample client to make HTTP requests + /// + public class SystemNetTokenHttpClient : TokenHttpClient + { +#if NET462 + private string PlatVersion = ".NET Framework 4.6.2+"; +#else + private string PlatVersion = RuntimeInformation.FrameworkDescription; +#endif + + private readonly System.Net.Http.HttpClient _httpClient; + + /// + /// Create new HttpClient + /// + /// HTTP client to use + public SystemNetTokenHttpClient(System.Net.Http.HttpClient httpClient = null) + { + _httpClient = httpClient ?? new System.Net.Http.HttpClient(new HttpClientHandler() { AllowAutoRedirect = false }); + } + + /// + /// Make a synchronous request + /// + /// Twilio request + /// Twilio response + public override Response MakeRequest(TokenRequest request) + { + try + { + var task = MakeRequestAsync(request); + task.Wait(); + return task.Result; + } + catch (AggregateException ae) + { + // Combine nested AggregateExceptions + ae = ae.Flatten(); + throw ae.InnerExceptions[0]; + } + } + + /// + /// Make an asynchronous request + /// + /// Twilio response + /// Task that resolves to the response + public override async Task MakeRequestAsync(TokenRequest request) + { + var httpRequest = BuildHttpRequest(request); + if (!Equals(request.Method, HttpMethod.Get)) + { + if (request.ContentType == null) + request.ContentType = EnumConstants.ContentTypeEnum.FORM_URLENCODED; + + if (Equals(request.ContentType, EnumConstants.ContentTypeEnum.JSON)) + httpRequest.Content = new StringContent(request.Body, Encoding.UTF8, "application/json"); + + else if (Equals(request.ContentType, EnumConstants.ContentTypeEnum.SCIM)) + httpRequest.Content = new StringContent(request.Body, Encoding.UTF8, "application/scim"); + + else if(Equals(request.ContentType, EnumConstants.ContentTypeEnum.FORM_URLENCODED)) + httpRequest.Content = new FormUrlEncodedContent(request.PostParams); + } + + this.LastRequest = request; + this.LastResponse = null; + + var httpResponse = await _httpClient.SendAsync(httpRequest).ConfigureAwait(false); + var reader = new StreamReader(await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false)); + + // Create and return a new Response. Keep a reference to the last + // response for debugging, but don't return it as it may be shared + // among threads. + var response = new Response(httpResponse.StatusCode, await reader.ReadToEndAsync().ConfigureAwait(false), httpResponse.Headers); + this.LastResponse = response; + return response; + } + + private HttpRequestMessage BuildHttpRequest(TokenRequest request) + { + var httpRequest = new HttpRequestMessage( + new System.Net.Http.HttpMethod(request.Method.ToString()), + request.ConstructUrl() + ); + + var authBytes = request.AccessToken; + 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(" "); + System.Text.StringBuilder PlatVersionSb= new System.Text.StringBuilder(PlatVersion); + PlatVersionSb[lastSpaceIndex] = '/'; + + string helperLibVersion = AssemblyInfomation.AssemblyInformationalVersion; + + string osName = "Unknown"; + osName = Environment.OSVersion.Platform.ToString(); + + string osArch; +#if !NET462 + osArch = RuntimeInformation.OSArchitecture.ToString(); +#else + osArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "Unknown"; +#endif + var libraryVersion = String.Format("twilio-csharp/{0} ({1} {2}) {3}", helperLibVersion, osName, osArch, PlatVersionSb); + + if (request.UserAgentExtensions != null) + { + foreach (var extension in request.UserAgentExtensions) + { + libraryVersion += " " + extension; + } + } + + httpRequest.Headers.TryAddWithoutValidation("User-Agent", libraryVersion); + + foreach (var header in request.HeaderParams) + { + httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return httpRequest; + } + } +} +#endif diff --git a/src/Twilio/Http/BearerToken/TokenHttpClient.cs b/src/Twilio/Http/BearerToken/TokenHttpClient.cs new file mode 100644 index 000000000..86e5ff79d --- /dev/null +++ b/src/Twilio/Http/BearerToken/TokenHttpClient.cs @@ -0,0 +1,41 @@ +using System; + +namespace Twilio.Http.BearerToken +{ + /// + /// Base http client used to make Twilio requests + /// + public abstract class TokenHttpClient + { + /// + /// The last request made by this client + /// + public TokenRequest LastRequest { get; protected set; } + + /// + /// The last response received by this client + /// + public Response LastResponse { get; protected set; } + + /// + /// Make a request to Twilio, returns non-2XX responses as well + /// + /// + /// request to make + /// throws exception on network or connection errors. + /// response of the request + public abstract Response MakeRequest(TokenRequest request); + +#if !NET35 + /// + /// Make an async request to Twilio, returns non-2XX responses as well + /// + /// + /// request to make + /// throws exception on network or connection errors. + /// response of the request + public abstract System.Threading.Tasks.Task MakeRequestAsync(TokenRequest request); +#endif + + } +} diff --git a/src/Twilio/Http/BearerToken/TokenManager.cs b/src/Twilio/Http/BearerToken/TokenManager.cs new file mode 100644 index 000000000..11275b71b --- /dev/null +++ b/src/Twilio/Http/BearerToken/TokenManager.cs @@ -0,0 +1,18 @@ +namespace Twilio.Http.BearerToken +{ + /// + /// Interface for a Token Manager + /// + public interface TokenManager + { + + /// + /// Fetch/Create an access token + /// + /// + /// access token fetched from token endpoint + string fetchAccessToken(); + + } +} + diff --git a/src/Twilio/Http/BearerToken/TokenRequest.cs b/src/Twilio/Http/BearerToken/TokenRequest.cs new file mode 100644 index 000000000..fbc9dc70e --- /dev/null +++ b/src/Twilio/Http/BearerToken/TokenRequest.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Twilio.Constant; +using Twilio.Rest; + +#if !NET35 +using System.Net; +#else +using System.Web; +#endif + +namespace Twilio.Http.BearerToken +{ + /// + /// Twilio request object with bearer token authentication + /// + public class TokenRequest + { + private static readonly string DEFAULT_REGION = "us1"; + + /// + /// HTTP Method + /// + public HttpMethod Method { get; } + + public Uri Uri { get; private set; } + + /// + /// Auth username + /// + public string AccessToken { get; set; } + + /// + /// Twilio region + /// + public string Region { get; set; } + + /// + /// Twilio edge + /// + public string Edge { get; set; } + + /// + /// Additions to the user agent string + /// + public string[] UserAgentExtensions { get; set; } + + /// + /// Query params + /// + public List> QueryParams { get; private set; } + + /// + /// Post params + /// + public List> PostParams { get; private set; } + + /// + /// Header params + /// + public List> HeaderParams { get; private set; } + + /// + /// Content Type + /// + public EnumConstants.ContentTypeEnum ContentType { get; set; } + + /// + /// Body + /// + public string Body { get; set; } + + /// + /// Create a new Twilio request + /// + /// HTTP Method + /// Request URL + public TokenRequest(HttpMethod method, string url) + { + Method = method; + Uri = new Uri(url); + QueryParams = new List>(); + PostParams = new List>(); + HeaderParams = new List>(); + } + + /// + /// Create a new Twilio request + /// + /// HTTP method + /// Twilio subdomain + /// Request URI + /// Twilio region + /// Query parameters + /// Post data + /// Twilio edge + /// Custom header data + /// Content Type + /// Request Body + public TokenRequest( + HttpMethod method, + Domain domain, + string uri, + string region = null, + List> queryParams = null, + List> postParams = null, + string edge = null, + List> headerParams = null, + EnumConstants.ContentTypeEnum contentType = null, + string body = null + ) + { + Method = method; + Uri = new Uri("https://" + domain + ".twilio.com" + uri); + Region = region; + Edge = edge; + + QueryParams = queryParams ?? new List>(); + PostParams = postParams ?? new List>(); + HeaderParams = headerParams ?? new List>(); + + ContentType = contentType; + Body = body; + } + + /// + /// Construct the request URL + /// + /// Built URL including query parameters + public Uri ConstructUrl() + { + var uri = buildUri(); + return QueryParams.Count > 0 ? + new Uri(uri.AbsoluteUri + "?" + EncodeParameters(QueryParams)) : + new Uri(uri.AbsoluteUri); + } + + public Uri buildUri() + { + if (Region != null || Edge != null) + { + var uriBuilder = new UriBuilder(Uri); + var pieces = uriBuilder.Host.Split('.'); + var product = pieces[0]; + var domain = String.Join(".", pieces.Skip(pieces.Length - 2).ToArray()); + + var region = Region; + var edge = Edge; + + if (pieces.Length == 4) // product.region.twilio.com + { + region = region ?? pieces[1]; + } + else if (pieces.Length == 5) // product.edge.region.twilio.com + { + edge = edge ?? pieces[1]; + region = region ?? pieces[2]; + } + + if (edge != null && region == null) + region = DEFAULT_REGION; + + string[] parts = { product, edge, region, domain }; + + uriBuilder.Host = String.Join(".", Array.FindAll(parts, part => !string.IsNullOrEmpty(part))); + return uriBuilder.Uri; + } + + return Uri; + } + + /// + /// Set auth for the request + /// + /// Auth accessToken + public void SetAuth(string accessToken) + { + AccessToken = accessToken; + } + + private static string EncodeParameters(IEnumerable> data) + { + var result = ""; + var first = true; + foreach (var pair in data) + { + if (first) + { + first = false; + } + else + { + result += "&"; + } + +#if !NET35 + result += WebUtility.UrlEncode(pair.Key) + "=" + WebUtility.UrlEncode(pair.Value); +#else + result += HttpUtility.UrlEncode(pair.Key) + "=" + HttpUtility.UrlEncode(pair.Value); +#endif + } + + return result; + } + + /// + /// Encode POST data for transfer + /// + /// Encoded byte array + public byte[] EncodePostParams() + { + return Encoding.UTF8.GetBytes(EncodeParameters(PostParams)); + } + + /// + /// Add query parameter to request + /// + /// name of parameter + /// value of parameter + public void AddQueryParam(string name, string value) + { + AddParam(QueryParams, name, value); + } + + /// + /// Add a parameter to the request payload + /// + /// name of parameter + /// value of parameter + public void AddPostParam(string name, string value) + { + AddParam(PostParams, name, value); + } + + /// + /// Add a header parameter + /// + /// name of parameter + /// value of parameter + public void AddHeaderParam(string name, string value) + { + AddParam(HeaderParams, name, value); + } + + private static void AddParam(ICollection> list, string name, string value) + { + list.Add(new KeyValuePair(name, value)); + } + + /// + /// Compare request + /// + /// object to compare to + /// true if requests are equal; false otherwise + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != typeof(Request)) + { + return false; + } + + var other = (Request)obj; + return Method.Equals(other.Method) && + buildUri().Equals(other.buildUri()) && + QueryParams.All(other.QueryParams.Contains) && + other.QueryParams.All(QueryParams.Contains) && + PostParams.All(other.PostParams.Contains) && + other.PostParams.All(PostParams.Contains) && + HeaderParams.All(other.HeaderParams.Contains) && + other.HeaderParams.All(HeaderParams.Contains); + } + + /// + /// Generate hash code for request + /// + /// generated hash code + public override int GetHashCode() + { + unchecked + { + return (Method?.GetHashCode() ?? 0) ^ + (buildUri()?.GetHashCode() ?? 0) ^ + (QueryParams?.GetHashCode() ?? 0) ^ + (PostParams?.GetHashCode() ?? 0) ^ + (HeaderParams?.GetHashCode() ?? 0); + } + } + } +} diff --git a/src/Twilio/Http/Net35/WebBearerTokenRequestClient.cs b/src/Twilio/Http/Net35/WebBearerTokenRequestClient.cs new file mode 100644 index 000000000..48c5f3aab --- /dev/null +++ b/src/Twilio/Http/Net35/WebBearerTokenRequestClient.cs @@ -0,0 +1,94 @@ +#if NET35 +using System.IO; +using Twilio.Http.BearerToken; + +namespace Twilio.Http.Net35 +{ + + /// + /// Sample client to make requests + /// + public class WebBearerTokenRequestClient : TokenHttpClient + { + private const string PlatVersion = ".NET/3.5"; + private HttpWebRequestFactory factory; + + public WebBearerTokenRequestClient(HttpWebRequestFactory factory = null) + { + this.factory = factory ?? new HttpWebRequestFactory(); + } + + /// + /// Make an HTTP request + /// + /// Twilio request + /// Twilio response + public override Response MakeRequest(TokenRequest request) + { + + IHttpWebRequestWrapper httpRequest = BuildHttpRequest(request); + if (!Equals(request.Method, HttpMethod.Get)) + { + httpRequest.WriteBody(request.EncodePostParams()); + } + + this.LastRequest = request; + this.LastResponse = null; + try + { + IHttpWebResponseWrapper response = httpRequest.GetResponse(); + string content = GetResponseContent(response); + this.LastResponse = new Response(response.StatusCode, content, response.Headers); + } + catch (WebExceptionWrapper e) + { + if (e.Response == null) + { + // Network or connection error + throw e.RealException; + } + // Non 2XX status code + IHttpWebResponseWrapper errorResponse = e.Response; + this.LastResponse = new Response(errorResponse.StatusCode, GetResponseContent(errorResponse), errorResponse.Headers); + } + return this.LastResponse; + } + + private string GetResponseContent(IHttpWebResponseWrapper response) + { + var reader = new StreamReader(response.GetResponseStream()); + return reader.ReadToEnd(); + } + + private IHttpWebRequestWrapper BuildHttpRequest(TokenRequest request) + { + IHttpWebRequestWrapper httpRequest = this.factory.Create(request.ConstructUrl()); + + string helperLibVersion = AssemblyInfomation.AssemblyInformationalVersion; + string osName = System.Environment.OSVersion.Platform.ToString(); + string osArch = System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "Unknown"; + var libraryVersion = System.String.Format("twilio-csharp/{0} ({1} {2}) {3}", helperLibVersion, osName, osArch, PlatVersion); + + if (request.UserAgentExtensions != null) + { + foreach (var extension in request.UserAgentExtensions) + { + libraryVersion += " " + extension; + } + } + + httpRequest.UserAgent = libraryVersion; + + httpRequest.Method = request.Method.ToString(); + httpRequest.Accept = "application/json"; + httpRequest.Headers["Accept-Encoding"] = "utf-8"; + + var authBytes = request.AccessToken; + httpRequest.Headers["Authorization"] = "Bearer " + authBytes; + httpRequest.ContentType = "application/x-www-form-urlencoded"; + + return httpRequest; + } + } +} +#endif \ No newline at end of file diff --git a/src/Twilio/Http/Net35/WebNoAuthRequestClient.cs b/src/Twilio/Http/Net35/WebNoAuthRequestClient.cs new file mode 100644 index 000000000..6abd23688 --- /dev/null +++ b/src/Twilio/Http/Net35/WebNoAuthRequestClient.cs @@ -0,0 +1,91 @@ +#if NET35 +using System.IO; +using Twilio.Http.NoAuth; + +namespace Twilio.Http.Net35 +{ + + /// + /// Sample client to make requests + /// + public class WebNoAuthRequestClient : NoAuthHttpClient + { + private const string PlatVersion = ".NET/3.5"; + private HttpWebRequestFactory factory; + + public WebNoAuthRequestClient(HttpWebRequestFactory factory = null) + { + this.factory = factory ?? new HttpWebRequestFactory(); + } + + /// + /// Make an HTTP request + /// + /// Twilio request + /// Twilio response + public override Response MakeRequest(NoAuthRequest request) + { + + IHttpWebRequestWrapper httpRequest = BuildHttpRequest(request); + if (!Equals(request.Method, HttpMethod.Get)) + { + httpRequest.WriteBody(request.EncodePostParams()); + } + + this.LastRequest = request; + this.LastResponse = null; + try + { + IHttpWebResponseWrapper response = httpRequest.GetResponse(); + string content = GetResponseContent(response); + this.LastResponse = new Response(response.StatusCode, content, response.Headers); + } + catch (WebExceptionWrapper e) + { + if (e.Response == null) + { + // Network or connection error + throw e.RealException; + } + // Non 2XX status code + IHttpWebResponseWrapper errorResponse = e.Response; + this.LastResponse = new Response(errorResponse.StatusCode, GetResponseContent(errorResponse), errorResponse.Headers); + } + return this.LastResponse; + } + + private string GetResponseContent(IHttpWebResponseWrapper response) + { + var reader = new StreamReader(response.GetResponseStream()); + return reader.ReadToEnd(); + } + + private IHttpWebRequestWrapper BuildHttpRequest(NoAuthRequest request) + { + IHttpWebRequestWrapper httpRequest = this.factory.Create(request.ConstructUrl()); + + string helperLibVersion = AssemblyInfomation.AssemblyInformationalVersion; + string osName = System.Environment.OSVersion.Platform.ToString(); + string osArch = System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "Unknown"; + var libraryVersion = System.String.Format("twilio-csharp/{0} ({1} {2}) {3}", helperLibVersion, osName, osArch, PlatVersion); + + if (request.UserAgentExtensions != null) + { + foreach (var extension in request.UserAgentExtensions) + { + libraryVersion += " " + extension; + } + } + + httpRequest.UserAgent = libraryVersion; + + httpRequest.Method = request.Method.ToString(); + httpRequest.Accept = "application/json"; + httpRequest.Headers["Accept-Encoding"] = "utf-8"; + httpRequest.ContentType = "application/x-www-form-urlencoded"; + + return httpRequest; + } + } +} +#endif \ No newline at end of file diff --git a/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs b/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs new file mode 100644 index 000000000..8cac2637c --- /dev/null +++ b/src/Twilio/Http/NoAuth/NoAuthHttpClient.cs @@ -0,0 +1,41 @@ +using System; + +namespace Twilio.Http.NoAuth +{ + /// + /// Base http client used to make Twilio requests + /// + public abstract class NoAuthHttpClient + { + /// + /// The last request made by this client + /// + public NoAuthRequest LastRequest { get; protected set; } + + /// + /// The last response received by this client + /// + public Response LastResponse { get; protected set; } + + /// + /// Make a request to Twilio, returns non-2XX responses as well + /// + /// + /// request to make + /// throws exception on network or connection errors. + /// response of the request + public abstract Response MakeRequest(NoAuthRequest request); + +#if !NET35 + /// + /// Make an async request to Twilio, returns non-2XX responses as well + /// + /// + /// request to make + /// throws exception on network or connection errors. + /// response of the request + public abstract System.Threading.Tasks.Task MakeRequestAsync(NoAuthRequest request); +#endif + + } +} diff --git a/src/Twilio/Http/NoAuth/NoAuthRequest.cs b/src/Twilio/Http/NoAuth/NoAuthRequest.cs new file mode 100644 index 000000000..f1ea6f400 --- /dev/null +++ b/src/Twilio/Http/NoAuth/NoAuthRequest.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Twilio.Constant; +using Twilio.Rest; + +#if !NET35 +using System.Net; +#else +using System.Web; +#endif + +namespace Twilio.Http.NoAuth +{ + /// + /// Twilio request object with bearer token authentication + /// + public class NoAuthRequest + { + private static readonly string DEFAULT_REGION = "us1"; + + /// + /// HTTP Method + /// + public HttpMethod Method { get; } + + public Uri Uri { get; private set; } + + + /// + /// Twilio region + /// + public string Region { get; set; } + + /// + /// Twilio edge + /// + public string Edge { get; set; } + + /// + /// Additions to the user agent string + /// + public string[] UserAgentExtensions { get; set; } + + /// + /// Query params + /// + public List> QueryParams { get; private set; } + + /// + /// Post params + /// + public List> PostParams { get; private set; } + + /// + /// Header params + /// + public List> HeaderParams { get; private set; } + + /// + /// Content Type + /// + public EnumConstants.ContentTypeEnum ContentType { get; set; } + + /// + /// Body + /// + public string Body { get; set; } + + /// + /// Create a new Twilio request + /// + /// HTTP Method + /// Request URL + public NoAuthRequest(HttpMethod method, string url) + { + Method = method; + Uri = new Uri(url); + QueryParams = new List>(); + PostParams = new List>(); + HeaderParams = new List>(); + } + + /// + /// Create a new Twilio request + /// + /// HTTP method + /// Twilio subdomain + /// Request URI + /// Twilio region + /// Query parameters + /// Post data + /// Twilio edge + /// Custom header data + /// Content Type + /// Request Body + public NoAuthRequest( + HttpMethod method, + Domain domain, + string uri, + string region = null, + List> queryParams = null, + List> postParams = null, + string edge = null, + List> headerParams = null, + EnumConstants.ContentTypeEnum contentType = null, + string body = null + ) + { + Method = method; + Uri = new Uri("https://" + domain + ".twilio.com" + uri); + Region = region; + Edge = edge; + + QueryParams = queryParams ?? new List>(); + PostParams = postParams ?? new List>(); + HeaderParams = headerParams ?? new List>(); + + ContentType = contentType; + Body = body; + } + + /// + /// Construct the request URL + /// + /// Built URL including query parameters + public Uri ConstructUrl() + { + var uri = buildUri(); + return QueryParams.Count > 0 ? + new Uri(uri.AbsoluteUri + "?" + EncodeParameters(QueryParams)) : + new Uri(uri.AbsoluteUri); + } + + public Uri buildUri() + { + if (Region != null || Edge != null) + { + var uriBuilder = new UriBuilder(Uri); + var pieces = uriBuilder.Host.Split('.'); + var product = pieces[0]; + var domain = String.Join(".", pieces.Skip(pieces.Length - 2).ToArray()); + + var region = Region; + var edge = Edge; + + if (pieces.Length == 4) // product.region.twilio.com + { + region = region ?? pieces[1]; + } + else if (pieces.Length == 5) // product.edge.region.twilio.com + { + edge = edge ?? pieces[1]; + region = region ?? pieces[2]; + } + + if (edge != null && region == null) + region = DEFAULT_REGION; + + string[] parts = { product, edge, region, domain }; + + uriBuilder.Host = String.Join(".", Array.FindAll(parts, part => !string.IsNullOrEmpty(part))); + return uriBuilder.Uri; + } + + return Uri; + } + + + private static string EncodeParameters(IEnumerable> data) + { + var result = ""; + var first = true; + foreach (var pair in data) + { + if (first) + { + first = false; + } + else + { + result += "&"; + } + +#if !NET35 + result += WebUtility.UrlEncode(pair.Key) + "=" + WebUtility.UrlEncode(pair.Value); +#else + result += HttpUtility.UrlEncode(pair.Key) + "=" + HttpUtility.UrlEncode(pair.Value); +#endif + } + + return result; + } + + /// + /// Encode POST data for transfer + /// + /// Encoded byte array + public byte[] EncodePostParams() + { + return Encoding.UTF8.GetBytes(EncodeParameters(PostParams)); + } + + /// + /// Add query parameter to request + /// + /// name of parameter + /// value of parameter + public void AddQueryParam(string name, string value) + { + AddParam(QueryParams, name, value); + } + + /// + /// Add a parameter to the request payload + /// + /// name of parameter + /// value of parameter + public void AddPostParam(string name, string value) + { + AddParam(PostParams, name, value); + } + + /// + /// Add a header parameter + /// + /// name of parameter + /// value of parameter + public void AddHeaderParam(string name, string value) + { + AddParam(HeaderParams, name, value); + } + + private static void AddParam(ICollection> list, string name, string value) + { + list.Add(new KeyValuePair(name, value)); + } + + /// + /// Compare request + /// + /// object to compare to + /// true if requests are equal; false otherwise + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != typeof(Request)) + { + return false; + } + + var other = (Request)obj; + return Method.Equals(other.Method) && + buildUri().Equals(other.buildUri()) && + QueryParams.All(other.QueryParams.Contains) && + other.QueryParams.All(QueryParams.Contains) && + PostParams.All(other.PostParams.Contains) && + other.PostParams.All(PostParams.Contains) && + HeaderParams.All(other.HeaderParams.Contains) && + other.HeaderParams.All(HeaderParams.Contains); + } + + /// + /// Generate hash code for request + /// + /// generated hash code + public override int GetHashCode() + { + unchecked + { + return (Method?.GetHashCode() ?? 0) ^ + (buildUri()?.GetHashCode() ?? 0) ^ + (QueryParams?.GetHashCode() ?? 0) ^ + (PostParams?.GetHashCode() ?? 0) ^ + (HeaderParams?.GetHashCode() ?? 0); + } + } + } +} diff --git a/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs b/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs new file mode 100644 index 000000000..ba2e58a76 --- /dev/null +++ b/src/Twilio/Http/NoAuth/SystemNetNoAuthHttpClient.cs @@ -0,0 +1,139 @@ +#if !NET35 +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Text; +using Twilio.Constant; + +namespace Twilio.Http.NoAuth +{ + /// + /// Sample client to make HTTP requests + /// + public class SystemNetNoAuthHttpClient : NoAuthHttpClient + { +#if NET462 + private string PlatVersion = ".NET Framework 4.6.2+"; +#else + private string PlatVersion = RuntimeInformation.FrameworkDescription; +#endif + + private readonly System.Net.Http.HttpClient _httpClient; + + /// + /// Create new HttpClient + /// + /// HTTP client to use + public SystemNetNoAuthHttpClient(System.Net.Http.HttpClient httpClient = null) + { + _httpClient = httpClient ?? new System.Net.Http.HttpClient(new HttpClientHandler() { AllowAutoRedirect = false }); + } + + /// + /// Make a synchronous request + /// + /// Twilio request + /// Twilio response + public override Response MakeRequest(NoAuthRequest request) + { + try + { + var task = MakeRequestAsync(request); + task.Wait(); + return task.Result; + } + catch (AggregateException ae) + { + // Combine nested AggregateExceptions + ae = ae.Flatten(); + throw ae.InnerExceptions[0]; + } + } + + /// + /// Make an asynchronous request + /// + /// Twilio response + /// Task that resolves to the response + public override async Task MakeRequestAsync(NoAuthRequest request) + { + var httpRequest = BuildHttpRequest(request); + if (!Equals(request.Method, HttpMethod.Get)) + { + if (request.ContentType == null) + request.ContentType = EnumConstants.ContentTypeEnum.FORM_URLENCODED; + + if (Equals(request.ContentType, EnumConstants.ContentTypeEnum.JSON)) + httpRequest.Content = new StringContent(request.Body, Encoding.UTF8, "application/json"); + + else if (Equals(request.ContentType, EnumConstants.ContentTypeEnum.SCIM)) + httpRequest.Content = new StringContent(request.Body, Encoding.UTF8, "application/scim"); + + else if(Equals(request.ContentType, EnumConstants.ContentTypeEnum.FORM_URLENCODED)) + httpRequest.Content = new FormUrlEncodedContent(request.PostParams); + } + + this.LastRequest = request; + this.LastResponse = null; + + var httpResponse = await _httpClient.SendAsync(httpRequest).ConfigureAwait(false); + var reader = new StreamReader(await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false)); + + // Create and return a new Response. Keep a reference to the last + // response for debugging, but don't return it as it may be shared + // among threads. + var response = new Response(httpResponse.StatusCode, await reader.ReadToEndAsync().ConfigureAwait(false), httpResponse.Headers); + this.LastResponse = response; + return response; + } + + private HttpRequestMessage BuildHttpRequest(NoAuthRequest request) + { + var httpRequest = new HttpRequestMessage( + new System.Net.Http.HttpMethod(request.Method.ToString()), + request.ConstructUrl() + ); + + httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + httpRequest.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("utf-8")); + + int lastSpaceIndex = PlatVersion.LastIndexOf(" "); + System.Text.StringBuilder PlatVersionSb= new System.Text.StringBuilder(PlatVersion); + PlatVersionSb[lastSpaceIndex] = '/'; + + string helperLibVersion = AssemblyInfomation.AssemblyInformationalVersion; + + string osName = "Unknown"; + osName = Environment.OSVersion.Platform.ToString(); + + string osArch; +#if !NET462 + osArch = RuntimeInformation.OSArchitecture.ToString(); +#else + osArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") ?? "Unknown"; +#endif + var libraryVersion = String.Format("twilio-csharp/{0} ({1} {2}) {3}", helperLibVersion, osName, osArch, PlatVersionSb); + + if (request.UserAgentExtensions != null) + { + foreach (var extension in request.UserAgentExtensions) + { + libraryVersion += " " + extension; + } + } + + httpRequest.Headers.TryAddWithoutValidation("User-Agent", libraryVersion); + + foreach (var header in request.HeaderParams) + { + httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return httpRequest; + } + } +} +#endif diff --git a/src/Twilio/Rest/PreviewIam/V1/TokenOptions.cs b/src/Twilio/Rest/PreviewIam/V1/TokenOptions.cs new file mode 100644 index 000000000..95adbbe9f --- /dev/null +++ b/src/Twilio/Rest/PreviewIam/V1/TokenOptions.cs @@ -0,0 +1,110 @@ +/* + * This code was generated by + * ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + * | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + * | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + * + * Organization Public API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +using System; +using System.Collections.Generic; +using Twilio.Base; +using Twilio.Converters; + + + + +namespace Twilio.Rest.PreviewIam.V1 +{ + + /// create + public class CreateTokenOptions : IOptions + { + + /// Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + public string GrantType { get; } + + /// A 34 character string that uniquely identifies this OAuth App. + public string ClientId { get; } + + /// The credential for confidential OAuth App. + public string ClientSecret { get; set; } + + /// JWT token related to the authorization code grant type. + public string Code { get; set; } + + /// The redirect uri + public string RedirectUri { get; set; } + + /// The targeted audience uri + public string Audience { get; set; } + + /// JWT token related to refresh access token. + public string RefreshToken { get; set; } + + /// The scope of token + public string Scope { get; set; } + + + /// Construct a new CreateTokenOptions + /// Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + /// A 34 character string that uniquely identifies this OAuth App. + public CreateTokenOptions(string grantType, string clientId) + { + GrantType = grantType; + ClientId = clientId; + } + + + /// Generate the necessary parameters + public List> GetParams() + { + var p = new List>(); + + if (GrantType != null) + { + p.Add(new KeyValuePair("grant_type", GrantType)); + } + if (ClientId != null) + { + p.Add(new KeyValuePair("client_id", ClientId)); + } + if (ClientSecret != null) + { + p.Add(new KeyValuePair("client_secret", ClientSecret)); + } + if (Code != null) + { + p.Add(new KeyValuePair("code", Code)); + } + if (RedirectUri != null) + { + p.Add(new KeyValuePair("redirect_uri", RedirectUri)); + } + if (Audience != null) + { + p.Add(new KeyValuePair("audience", Audience)); + } + if (RefreshToken != null) + { + p.Add(new KeyValuePair("refresh_token", RefreshToken)); + } + if (Scope != null) + { + p.Add(new KeyValuePair("scope", Scope)); + } + return p; + } + + + + } +} + diff --git a/src/Twilio/Rest/PreviewIam/V1/TokenResource.cs b/src/Twilio/Rest/PreviewIam/V1/TokenResource.cs new file mode 100644 index 000000000..9f79ada3c --- /dev/null +++ b/src/Twilio/Rest/PreviewIam/V1/TokenResource.cs @@ -0,0 +1,195 @@ +/* + * This code was generated by + * ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + * | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + * | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + * + * Organization Public API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Twilio.Base; +using Twilio.Clients; +using Twilio.Constant; +using Twilio.Converters; +using Twilio.Exceptions; +using Twilio.Http; + + +using Twilio.Clients.NoAuth; +using Twilio.Http.NoAuth; + + +namespace Twilio.Rest.PreviewIam.V1 +{ + public class TokenResource : Resource + { + + + + + + private static NoAuthRequest BuildCreateRequest(CreateTokenOptions options, TwilioNoAuthRestClient client) + { + + string path = "/v1/token"; + + + return new NoAuthRequest( + HttpMethod.Post, + Rest.Domain.PreviewIam, + path, + contentType: EnumConstants.ContentTypeEnum.FORM_URLENCODED, + postParams: options.GetParams(), + headerParams: null + ); + } + + /// create + /// Create Token parameters + /// Client to make requests to Twilio + /// A single instance of Token + public static TokenResource Create(CreateTokenOptions options, TwilioNoAuthRestClient client = null) + { + client = client ?? TwilioOrgsTokenAuthClient.GetNoAuthRestClient(); + var response = client.Request(BuildCreateRequest(options, client)); + return FromJson(response.Content); + } + + #if !NET35 + /// create + /// Create Token parameters + /// Client to make requests to Twilio + /// Task that resolves to A single instance of Token + public static async System.Threading.Tasks.Task CreateAsync(CreateTokenOptions options, TwilioNoAuthRestClient client = null) + { + client = client ?? TwilioOrgsTokenAuthClient.GetNoAuthRestClient(); + var response = await client.RequestAsync(BuildCreateRequest(options, client)); + return FromJson(response.Content); + } + #endif + + /// create + /// Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + /// A 34 character string that uniquely identifies this OAuth App. + /// The credential for confidential OAuth App. + /// JWT token related to the authorization code grant type. + /// The redirect uri + /// The targeted audience uri + /// JWT token related to refresh access token. + /// The scope of token + /// Client to make requests to Twilio + /// A single instance of Token + public static TokenResource Create( + string grantType, + string clientId, + string clientSecret = null, + string code = null, + string redirectUri = null, + string audience = null, + string refreshToken = null, + string scope = null, + TwilioNoAuthRestClient client = null) + { + var options = new CreateTokenOptions(grantType, clientId){ ClientSecret = clientSecret, Code = code, RedirectUri = redirectUri, Audience = audience, RefreshToken = refreshToken, Scope = scope }; + return Create(options, client); + } + + #if !NET35 + /// create + /// Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + /// A 34 character string that uniquely identifies this OAuth App. + /// The credential for confidential OAuth App. + /// JWT token related to the authorization code grant type. + /// The redirect uri + /// The targeted audience uri + /// JWT token related to refresh access token. + /// The scope of token + /// Client to make requests to Twilio + /// Task that resolves to A single instance of Token + public static async System.Threading.Tasks.Task CreateAsync( + string grantType, + string clientId, + string clientSecret = null, + string code = null, + string redirectUri = null, + string audience = null, + string refreshToken = null, + string scope = null, + TwilioNoAuthRestClient client = null) + { + var options = new CreateTokenOptions(grantType, clientId){ ClientSecret = clientSecret, Code = code, RedirectUri = redirectUri, Audience = audience, RefreshToken = refreshToken, Scope = scope }; + return await CreateAsync(options, client); + } + #endif + + /// + /// Converts a JSON string into a TokenResource object + /// + /// Raw JSON string + /// TokenResource object represented by the provided JSON + public static TokenResource FromJson(string json) + { + try + { + return JsonConvert.DeserializeObject(json); + } + catch (JsonException e) + { + throw new ApiException(e.Message, e); + } + } + /// + /// Converts an object into a json string + /// + /// C# model + /// JSON string + public static string ToJson(object model) + { + try + { + return JsonConvert.SerializeObject(model); + } + catch (JsonException e) + { + throw new ApiException(e.Message, e); + } + } + + + /// Token which carries the necessary information to access a Twilio resource directly. + [JsonProperty("access_token")] + public string AccessToken { get; private set; } + + /// Token which carries the information necessary to get a new access token. + [JsonProperty("refresh_token")] + public string RefreshToken { get; private set; } + + /// Token which carries the information necessary of user profile. + [JsonProperty("id_token")] + public string IdToken { get; private set; } + + /// Token type + [JsonProperty("token_type")] + public string TokenType { get; private set; } + + /// The expires_in + [JsonProperty("expires_in")] + public long? ExpiresIn { get; private set; } + + + + private TokenResource() { + + } + } +} + diff --git a/src/Twilio/Twilio.csproj b/src/Twilio/Twilio.csproj index 0b08004fb..958b55a65 100644 --- a/src/Twilio/Twilio.csproj +++ b/src/Twilio/Twilio.csproj @@ -28,6 +28,10 @@ + + + + diff --git a/src/Twilio/TwilioOrgsTokenAuth.cs b/src/Twilio/TwilioOrgsTokenAuth.cs new file mode 100644 index 000000000..0593af2e4 --- /dev/null +++ b/src/Twilio/TwilioOrgsTokenAuth.cs @@ -0,0 +1,232 @@ +using Twilio.Clients; +using Twilio.Clients.NoAuth; +using Twilio.Clients.BearerToken; +using Twilio.Exceptions; +using Twilio.Http.BearerToken; +using Twilio.Annotations; + + +namespace Twilio +{ + /// + /// Default Twilio Client for bearer token authentication + /// + [Beta] + public class TwilioOrgsTokenAuthClient + { + private static string _accessToken; + private static string _region; + private static string _edge; + private static TwilioOrgsTokenRestClient _restClient; + private static TwilioNoAuthRestClient _noAuthRestClient; + private static string _logLevel; + private static TokenManager _tokenManager; + private static string _clientId; + private static string _clientSecret; + + private TwilioOrgsTokenAuthClient() { } + + /// + /// Initialize base client with username and password + /// + public static void Init(string clientId, string clientSecret) + { + SetClientId(clientId); + SetClientSecret(clientSecret); + SetTokenManager(new OrgsTokenManager(clientId, clientSecret)); + } + + + /// + /// Initialize base client + /// + public static void Init(string clientId, string clientSecret, + string code = null, + string redirectUri = null, + string audience = null, + string refreshToken = null, + string scope = null) + { + SetClientId(clientId); + SetClientSecret(clientSecret); + SetTokenManager(new OrgsTokenManager(clientId, clientSecret, code, redirectUri, audience, refreshToken, scope)); + } + + /// + /// Set the token manager + /// + /// token manager + public static void SetTokenManager(TokenManager tokenManager) + { + if (tokenManager == null) + { + throw new AuthenticationException("Token Manager can not be null"); + } + + if (tokenManager != _tokenManager) + { + Invalidate(); + } + + _tokenManager = tokenManager; + } + + /// + /// Set the client id + /// + /// client id of the organisation + public static void SetClientId(string clientId) + { + if (clientId == null) + { + throw new AuthenticationException("Client Id can not be null"); + } + + if (clientId != _clientId) + { + Invalidate(); + } + + _clientId = clientId; + } + + /// + /// Set the client secret + /// + /// client secret of the organisation + public static void SetClientSecret(string clientSecret) + { + if (clientSecret == null) + { + throw new AuthenticationException("Client Secret can not be null"); + } + + if (clientSecret != _clientSecret) + { + Invalidate(); + } + + _clientSecret = clientSecret; + } + + /// + /// Set the client region + /// + /// Client region + public static void SetRegion(string region) + { + if (region != _region) + { + Invalidate(); + InvalidateNoAuthClient(); + } + _region = region; + } + + /// + /// Set the client edge + /// + /// Client edge + public static void SetEdge(string edge) + { + if (edge != _edge) + { + Invalidate(); + InvalidateNoAuthClient(); + } + _edge = edge; + } + + /// + /// Set the logging level + /// + /// log level + public static void SetLogLevel(string loglevel) + { + if (loglevel != _logLevel) + { + Invalidate(); + InvalidateNoAuthClient(); + } + _logLevel = loglevel; + } + + /// + /// Get the rest client + /// + /// The rest client + public static TwilioOrgsTokenRestClient GetRestClient() + { + if (_restClient != null) + { + return _restClient; + } + + if (_tokenManager == null) + { + throw new AuthenticationException( + "TwilioBearerTokenRestClient was used before token manager was set, please call TwilioClient.init()" + ); + } + + _restClient = new TwilioOrgsTokenRestClient(_tokenManager, region: _region, edge: _edge) + { + LogLevel = _logLevel + }; + return _restClient; + } + + + /// + /// Get the noauth rest client + /// + /// The not auth rest client + public static TwilioNoAuthRestClient GetNoAuthRestClient() + { + if (_noAuthRestClient != null) + { + return _noAuthRestClient; + } + + _noAuthRestClient = new TwilioNoAuthRestClient(region: _region, edge: _edge) + { + LogLevel = _logLevel + }; + return _noAuthRestClient; + } + + /// + /// Set the rest client + /// + /// Rest Client to use + public static void SetRestClient(TwilioOrgsTokenRestClient restClient) + { + _restClient = restClient; + } + + /// + /// Clear out the Rest Client + /// + public static void Invalidate() + { + _restClient = null; + } + + /// + /// Clear out the Rest Client + /// + public static void InvalidateNoAuthClient() + { + _noAuthRestClient = 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 + /// + public static void ValidateSslCertificate() + { + TwilioRestClient.ValidateSslCertificate(); + } + } +}