diff --git a/Bynder/Api/BynderApiFactory.cs b/Bynder/Api/BynderApiFactory.cs deleted file mode 100644 index 97abdff..0000000 --- a/Bynder/Api/BynderApiFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using Bynder.Api.Impl; - -namespace Bynder.Api -{ - /// - /// Static class to create a instance - /// - public static class BynderApiFactory - { - /// - /// Create a using the given settings - /// - /// Settings to configure Bynder API - /// instance - public static IBynderApi Create(Settings settings) - { - return BynderApi.Create(settings); - } - } -} diff --git a/Bynder/Api/IBynderAPI.cs b/Bynder/Api/IBynderAPI.cs deleted file mode 100644 index ab8240e..0000000 --- a/Bynder/Api/IBynderAPI.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Threading.Tasks; -using Bynder.Models; - -namespace Bynder.Api -{ - /// - /// Interface to login to Bynder and then get instance of - /// - public interface IBynderApi : IDisposable - { - /// - /// Log in using API. To be able to use this method we need to provide an access token/secret with login permissions in - /// - /// email/username - /// password - /// Task with the user information - /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task LoginAsync(string email, string password); - - /* - To Login using the browser so only consumer key/secret are needed. Client has to do the following: - 1. Request temporary Tokens -> GetRequestTokenAsync - 2. Get Authorized Url to open the browser -> GetAuthorizeUrl - 3. Wait until user enters credentials and browser is redirected to callback Url. - 4. Request final access tokens -> GetAccessTokenAsync - - Example can be found in Bynder.Sample project - */ - - /// - /// Gets access token once the user has already logged in through the browser, - /// - /// Task to represent the access token process - /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task GetAccessTokenAsync(); - - /// - /// Gets temporary tokens to use when login through the browser - /// - /// Task to represent the request of temp tokens - /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task GetRequestTokenAsync(); - - /// - /// Gets the Url we need to open with the browser so the user can login and - /// we can exchange temp tokes for final access tokens - /// - /// Callback Url to be redirected when login is successful - /// string with the Url we need to open the browser with - string GetAuthorizeUrl(string callbackUrl); - - /// - /// Logout resets your credential. If the access token/secret provided in the have full permission, - /// even after this call, calls to all API functions will still work - /// - void Logout(); - - /// - /// Get asset bank manager to perform asset bank operations - /// - /// instance of asset manager. - IAssetBankManager GetAssetBankManager(); - - /// - /// Get collection manager to perform collections operations - /// - /// instance of collections manager - ICollectionsManager GetCollectionsManager(); - } -} diff --git a/Bynder/Api/Impl/AssetBankManager.cs b/Bynder/Api/Impl/AssetBankManager.cs deleted file mode 100644 index 35427e3..0000000 --- a/Bynder/Api/Impl/AssetBankManager.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Impl.Upload; -using Bynder.Api.Queries; -using Bynder.Models; - -namespace Bynder.Api.Impl -{ - /// - /// Implementation of - /// - internal class AssetBankManager : IAssetBankManager - { - /// - /// Request sender to communicate with the Bynder API - /// - private IOauthRequestSender _requestSender; - - /// - /// Instance to upload file to Bynder - /// - private FileUploader _uploader; - - /// - /// Initializes a new instance of the class - /// - /// instance to communicate with the Bynder API - public AssetBankManager(IOauthRequestSender requestSender) - { - _requestSender = requestSender; - _uploader = FileUploader.Create(_requestSender); - } - - /// - /// Check for more information - /// - /// Check for more information - public Task> GetBrandsAsync() - { - var request = new Request> - { - Uri = "/api/v4/brands/", - HTTPMethod = HttpMethod.Get - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - public Task> GetMetapropertiesAsync() - { - var request = new Request> - { - Uri = "/api/v4/metaproperties/", - HTTPMethod = HttpMethod.Get - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task> RequestMediaListAsync(MediaQuery query) - { - var request = new Request> - { - Uri = "/api/v4/media/", - HTTPMethod = HttpMethod.Get, - Query = query - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public async Task GetDownloadFileUrlAsync(DownloadMediaQuery query) - { - string uri = string.Empty; - if (query.MediaItemId == null) - { - uri = $"/api/v4/media/{query.MediaId}/download/"; - } - else - { - uri = $"/api/v4/media/{query.MediaId}/download/{query.MediaItemId}/"; - } - - var request = new Request - { - Uri = uri, - HTTPMethod = HttpMethod.Get - }; - - var downloadFileInformation = await _requestSender.SendRequestAsync(request).ConfigureAwait(false); - return downloadFileInformation.S3File; - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task UploadFileAsync(UploadQuery query) - { - return _uploader.UploadFile(query); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task RequestMediaInfoAsync(MediaInformationQuery query) - { - var request = new Request - { - Uri = $"/api/v4/media/{query.MediaId}/", - HTTPMethod = HttpMethod.Get, - Query = query - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task ModifyMediaAsync(ModifyMediaQuery query) - { - var request = new Request - { - Uri = $"/api/v4/media/{query.MediaId}/", - HTTPMethod = HttpMethod.Post, - Query = query, - DeserializeResponse = false - }; - - return _requestSender.SendRequestAsync(request); - } - } -} diff --git a/Bynder/Api/Impl/BynderAPI.cs b/Bynder/Api/Impl/BynderAPI.cs deleted file mode 100644 index 9fbde31..0000000 --- a/Bynder/Api/Impl/BynderAPI.cs +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Specialized; -using System.Net.Http; -using System.Threading.Tasks; -using System.Web; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Models; - -namespace Bynder.Api.Impl -{ - /// - /// Implementation of - /// - internal class BynderApi : IBynderApi - { - /// - /// Instance to communicate with the Bynder API - /// - private IOauthRequestSender _requestSender; - - /// - /// Credentials to use to login and to generate the AuthorizeUrl - /// - private Credentials _credentials; - - /// - /// Base Url needed to generate AuthorizeUrl - /// - private string _baseUrl; - - /// - /// Instance of the asset bank manager - /// - private IAssetBankManager _assetBankManager; - - /// - /// Instance of the collections manager - /// - private ICollectionsManager _mediaCollectionsManager; - - /// - /// Initializes a new instance of the class - /// - /// Credentials to use to call the API - /// Base Url where we want to point API calls - /// Instance to communicate with Bynder API - public BynderApi(Credentials credentials, string baseUrl, IOauthRequestSender requestSender) - { - _credentials = credentials; - _baseUrl = baseUrl; - _requestSender = requestSender; - } - - /// - /// Creates an instance of using settins parameter - /// - /// settings to correctly configure instance - /// Bynder API instance to communicate with Bynder - public static IBynderApi Create(Settings settings) - { - var credentials = new Credentials( - settings.CONSUMER_KEY, - settings.CONSUMER_SECRET, - settings.TOKEN, - settings.TOKEN_SECRET); - - return new BynderApi(credentials, settings.URL, new OauthRequestSender(credentials, settings.URL)); - } - - /// - /// Check for more information - /// - /// Check for more information - public async Task GetRequestTokenAsync() - { - var request = new Request - { - Uri = "/api/v4/oauth/request_token", - HTTPMethod = HttpMethod.Post, - DeserializeResponse = false - }; - - var response = await _requestSender.SendRequestAsync(request).ConfigureAwait(false); - SetCredentialsFromResponse(response); - } - - /// - /// Check for more information - /// - /// Check for more information - public async Task GetAccessTokenAsync() - { - var request = new Request - { - Uri = "/api/v4/oauth/access_token", - HTTPMethod = HttpMethod.Post, - DeserializeResponse = false - }; - - var response = await _requestSender.SendRequestAsync(request).ConfigureAwait(false); - SetCredentialsFromResponse(response); - } - - /// - /// Check for more information - /// - /// Check for more information - public IAssetBankManager GetAssetBankManager() - { - if (_assetBankManager == null) - { - _assetBankManager = new AssetBankManager(_requestSender); - } - - return _assetBankManager; - } - - /// - /// Check for more information - /// - /// Check for more information - public ICollectionsManager GetCollectionsManager() - { - if (_mediaCollectionsManager == null) - { - _mediaCollectionsManager = new CollectionsManager(_requestSender); - } - - return _mediaCollectionsManager; - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public string GetAuthorizeUrl(string callbackUrl) - { - UriBuilder builder = new UriBuilder(_baseUrl); - NameValueCollection parameters = HttpUtility.ParseQueryString(string.Empty); - parameters.Add("oauth_token", _credentials.ACCESS_TOKEN); - - if (!string.IsNullOrEmpty(callbackUrl)) - { - parameters.Add("callback", callbackUrl); - } - - builder.Path += "api/v4/oauth/authorise/"; - builder.Query = parameters.ToString(); - return builder.ToString(); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - /// Check for more information - [Obsolete("This method of login is deprecated. We should login through the browser")] - public Task LoginAsync(string email, string password) - { - return LoginAsync(new LoginQuery - { - Username = email, - Password = password, - ConsumerId = _credentials.CONSUMER_KEY - }); - } - - /// - /// Check for more information - /// - public void Logout() - { - _credentials.Reset(); - } - - /// - /// Disposes the request sender - /// - public void Dispose() - { - _requestSender.Dispose(); - } - - /// - /// Helper method to get string response and update the credentials - /// - /// response string - private void SetCredentialsFromResponse(string response) - { - string token = HttpUtility.ParseQueryString(response).Get("oauth_token"); - string tokenSecret = HttpUtility.ParseQueryString(response).Get("oauth_token_secret"); - if (token != null - && tokenSecret != null) - { - _credentials.Set(token, tokenSecret); - } - } - - /// - /// Returns Task containing user information. Sends the request to - /// - /// information to call login API - /// Task with User information - private async Task LoginAsync(LoginQuery query) - { - var request = new Request - { - Uri = "/api/v4/users/login/", - HTTPMethod = HttpMethod.Post, - Query = query - }; - - var user = await _requestSender.SendRequestAsync(request); - _credentials.Set(user.TokenKey, user.TokenSecret); - - return user; - } - } -} diff --git a/Bynder/Api/Impl/CollectionsManager.cs b/Bynder/Api/Impl/CollectionsManager.cs deleted file mode 100644 index 5e0a21c..0000000 --- a/Bynder/Api/Impl/CollectionsManager.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Api.Queries.Collections; -using Bynder.Models; - -namespace Bynder.Api.Impl -{ - /// - /// Implementation of - /// - internal class CollectionsManager : ICollectionsManager - { - /// - /// Request sender to communicate with the Bynder API - /// - private IOauthRequestSender _requestSender; - - /// - /// Initializes a new instance of the class - /// - /// instance to communicate with the Bynder API - public CollectionsManager(IOauthRequestSender requestSender) - { - _requestSender = requestSender; - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task CreateCollectionAsync(CreateCollectionQuery query) - { - var request = new Request - { - Uri = $"/api/v4/collections/", - HTTPMethod = HttpMethod.Post, - Query = query, - DeserializeResponse = false - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task DeleteCollectionAsync(string id) - { - var request = new Request - { - Uri = $"/api/v4/collections/{id}/", - HTTPMethod = HttpMethod.Delete - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task GetCollectionAsync(string id) - { - var request = new Request - { - Uri = $"/api/v4/collections/{id}/", - HTTPMethod = HttpMethod.Get - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task> GetCollectionsAsync(GetCollectionsQuery query) - { - var request = new Request> - { - Uri = "/api/v4/collections/", - HTTPMethod = HttpMethod.Get, - Query = query - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task> GetMediaAsync(GetMediaQuery query) - { - var request = new Request> - { - Uri = $"/api/v4/collections/{query.CollectionId}/media/", - HTTPMethod = HttpMethod.Get - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task AddMediaAsync(AddMediaQuery query) - { - var request = new Request - { - Uri = $"/api/v4/collections/{query.CollectionId}/media/", - HTTPMethod = HttpMethod.Post, - Query = query, - DeserializeResponse = false - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task RemoveMediaAsync(RemoveMediaQuery query) - { - var request = new Request - { - Uri = $"/api/v4/collections/{query.CollectionId}/media/", - HTTPMethod = HttpMethod.Delete, - Query = query - }; - - return _requestSender.SendRequestAsync(request); - } - - /// - /// Check for more information - /// - /// Check for more information - /// Check for more information - public Task ShareCollectionAsync(ShareQuery query) - { - var request = new Request - { - Uri = $"/api/v4/collections/{query.CollectionId}/share/", - HTTPMethod = HttpMethod.Post, - Query = query, - DeserializeResponse = false - }; - - return _requestSender.SendRequestAsync(request); - } - } -} diff --git a/Bynder/Api/Impl/Oauth/Credentials.cs b/Bynder/Api/Impl/Oauth/Credentials.cs deleted file mode 100644 index 499656a..0000000 --- a/Bynder/Api/Impl/Oauth/Credentials.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -namespace Bynder.Api.Impl.Oauth -{ - /// - /// Class to hold token credentials to call the API. - /// - internal class Credentials - { - /// - /// Initializes new instance with specified values - /// - /// consumer key - /// consumer secret - /// token. This can be null if we are going to log in into Bynder through the browser - /// token secret. This can be null if we are going to log in into Bynder through the browser - public Credentials(string consumerKey, string consumerSecret, string token, string tokenSecret) - { - CONSUMER_KEY = consumerKey; - CONSUMER_SECRET = consumerSecret; - ACCESS_TOKEN = token; - ACCESS_TOKEN_SECRET = tokenSecret; - - INITIAL_TOKEN = token; - INITIAL_SECRET = tokenSecret; - } - - /// - /// Gets the consumer key - /// - public string CONSUMER_KEY { get; private set; } - - /// - /// Gets the consumer secret - /// - public string CONSUMER_SECRET { get; private set; } - - /// - /// Gets the access token - /// - public string ACCESS_TOKEN { get; private set; } - - /// - /// Gets the access token secret - /// - public string ACCESS_TOKEN_SECRET { get; private set; } - - /// - /// Initial token. Used when we want to reset credentials - /// - private string INITIAL_TOKEN { get; set; } - - /// - /// Initial token secret. Used when we want to reset credentials - /// - private string INITIAL_SECRET { get; set; } - - /// - /// Resets access token/secret to the initial ones. - /// - public void Reset() - { - ACCESS_TOKEN = INITIAL_TOKEN; - ACCESS_TOKEN_SECRET = INITIAL_SECRET; - } - - /// - /// Sets new access token/secret - /// - /// new access token - /// new access secret - public void Set(string token, string secret) - { - ACCESS_TOKEN = token; - ACCESS_TOKEN_SECRET = secret; - } - } -} diff --git a/Bynder/Api/Impl/Oauth/IOauthRequestSender.cs b/Bynder/Api/Impl/Oauth/IOauthRequestSender.cs deleted file mode 100644 index 497a0ab..0000000 --- a/Bynder/Api/Impl/Oauth/IOauthRequestSender.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Threading.Tasks; -using Bynder.Api.Queries; - -namespace Bynder.Api.Impl.Oauth -{ - /// - /// Interface to send oauth to the API. - /// - internal interface IOauthRequestSender : IDisposable - { - /// - /// Sends the request to Bynder API. It gets all necessary information from - /// and deserializes response if needed to specific object. - /// - /// Type we want to deserialize response to - /// Request with the information to do the API call - /// Task returning T - Task SendRequestAsync(Request request); - } -} \ No newline at end of file diff --git a/Bynder/Api/Impl/Oauth/OAuthBase.cs b/Bynder/Api/Impl/Oauth/OAuthBase.cs deleted file mode 100644 index d4c5f71..0000000 --- a/Bynder/Api/Impl/Oauth/OAuthBase.cs +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; - -namespace Bynder.Api.Impl.Oauth -{ - /// - /// Class to compute the OAuth header to sign requests. - /// Implementation from https://code.msdn.microsoft.com/Extending-HttpClient-with-8739f267/view/SourceCode - /// - internal class OAuthBase - { - private const string OAuthVersion = "1.0"; - private const string OAuthParameterPrefix = "oauth_"; - - // List of known and used oauth parameters' names - private const string OAuthConsumerKeyKey = "oauth_consumer_key"; - private const string OAuthVersionKey = "oauth_version"; - private const string OAuthSignatureMethodKey = "oauth_signature_method"; - private const string OAuthSignatureKey = "oauth_signature"; - private const string OAuthTimestampKey = "oauth_timestamp"; - private const string OAuthNonceKey = "oauth_nonce"; - private const string OAuthTokenKey = "oauth_token"; - private const string OAuthTokenSecretKey = "oauth_token_secret"; - - private const string HMACSHA1SignatureType = "HMAC-SHA1"; - private const string PlainTextSignatureType = "PLAINTEXT"; - private const string RSASHA1SignatureType = "RSA-SHA1"; - private const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; - - private Random random = new Random(); - - /// - /// Provides a predefined set of algorithms that are supported officially by the protocol - /// - public enum SignatureTypes - { - HMACSHA1, - PLAINTEXT, - RSASHA1 - } - - /// - /// Generate the signature base that is used to produce the signature - /// - /// The full url that needs to be signed including its non OAuth url parameters - /// The consumer key - /// The token, if available. If not available pass null or an empty string - /// The token secret, if available. If not available pass null or an empty string - /// The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc) - /// The signature type. To use the default values use OAuthBase.SignatureTypes. - /// The signature base - public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters) - { - if (token == null) - { - token = string.Empty; - } - - if (tokenSecret == null) - { - tokenSecret = string.Empty; - } - - if (string.IsNullOrEmpty(consumerKey)) - { - throw new ArgumentNullException("consumerKey"); - } - - if (string.IsNullOrEmpty(httpMethod)) - { - throw new ArgumentNullException("httpMethod"); - } - - if (string.IsNullOrEmpty(signatureType)) - { - throw new ArgumentNullException("signatureType"); - } - - normalizedUrl = null; - normalizedRequestParameters = null; - - List parameters = GetQueryParameters(url.Query); - parameters.Add(new QueryParameter(OAuthVersionKey, OAuthVersion)); - parameters.Add(new QueryParameter(OAuthNonceKey, nonce)); - parameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp)); - parameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType)); - parameters.Add(new QueryParameter(OAuthConsumerKeyKey, consumerKey)); - - if (!string.IsNullOrEmpty(token)) - { - parameters.Add(new QueryParameter(OAuthTokenKey, token)); - } - - parameters.Sort(new QueryParameterComparer()); - - normalizedUrl = string.Format("{0}://{1}", url.Scheme, url.Host); - if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) - { - normalizedUrl += ":" + url.Port; - } - - normalizedUrl += url.AbsolutePath; - normalizedRequestParameters = NormalizeRequestParameters(parameters); - - StringBuilder signatureBase = new StringBuilder(); - signatureBase.AppendFormat("{0}&", httpMethod.ToUpper()); - signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl)); - signatureBase.AppendFormat("{0}", UrlEncode(normalizedRequestParameters)); - - return signatureBase.ToString(); - } - - /// - /// Generate the signature value based on the given signature base and hash algorithm - /// - /// The signature based as produced by the GenerateSignatureBase method or by any other means - /// The hash algorithm used to perform the hashing. If the hashing algorithm requires initialization or a key it should be set prior to calling this method - /// A base64 string of the hash value - public string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash) - { - return ComputeHash(hash, signatureBase); - } - - /// - /// Generates a signature using the HMAC-SHA1 algorithm - /// - /// The full url that needs to be signed including its non OAuth url parameters - /// The consumer key - /// The consumer secret - /// The token, if available. If not available pass null or an empty string - /// The token secret, if available. If not available pass null or an empty string - /// The http method used. Must be a valid HTTP method verb (POST,GET,PUT,etc) - /// A base64 string of the hash value - public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, out string normalizedUrl, out string normalizedRequestParameters, out string authHeader) - { - return GenerateSignature(url, consumerKey, consumerSecret, token, tokenSecret, httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, out normalizedUrl, out normalizedRequestParameters, out authHeader); - } - - /// - /// Generates a signature using the specified signatureType - /// - /// The full url that needs to be signed including its non OAuth url parameters - /// The consumer key - /// The consumer secret - /// The token, if available. If not available pass null or an empty string - /// The token secret, if available. If not available pass null or an empty string - /// The http method used. Must be a valid HTTP method verb (POST,GET,PUT,etc) - /// The type of signature to use - /// A base64 string of the hash value - public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, SignatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters, out string authHeader) - { - normalizedUrl = null; - normalizedRequestParameters = null; - authHeader = null; - - switch (signatureType) - { - case SignatureTypes.PLAINTEXT: - return UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret)); - case SignatureTypes.HMACSHA1: - string signatureBase = GenerateSignatureBase(url, consumerKey, token, tokenSecret, httpMethod, timeStamp, nonce, HMACSHA1SignatureType, out normalizedUrl, out normalizedRequestParameters); - - HMACSHA1 hmacsha1 = new HMACSHA1(); - hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : UrlEncode(tokenSecret))); - - string signature = GenerateSignatureUsingHash(signatureBase, hmacsha1); - - StringBuilder auth = new StringBuilder(); - auth.AppendFormat("{0}=\"{1}\", ", OAuthConsumerKeyKey, UrlEncode(consumerKey)); - if (!string.IsNullOrEmpty(token)) - { - auth.AppendFormat("{0}=\"{1}\", ", OAuthTokenKey, UrlEncode(token)); - } - auth.AppendFormat("{0}=\"{1}\", ", OAuthSignatureMethodKey, "HMAC-SHA1"); - auth.AppendFormat("{0}=\"{1}\", ", OAuthTimestampKey, timeStamp); - auth.AppendFormat("{0}=\"{1}\", ", OAuthNonceKey, UrlEncode(nonce)); - auth.AppendFormat("{0}=\"{1}\", ", OAuthVersionKey, "1.0"); - auth.AppendFormat("{0}=\"{1}\"", OAuthSignatureKey, UrlEncode(signature)); - - authHeader = auth.ToString(); - - return signature; - - case SignatureTypes.RSASHA1: - throw new NotImplementedException(); - default: - throw new ArgumentException("Unknown signature type", "signatureType"); - } - } - - /// - /// Generate the timestamp for the signature - /// - /// - public virtual string GenerateTimeStamp() - { - // Default implementation of UNIX time of the current UTC time - TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); - return Convert.ToInt64(ts.TotalSeconds).ToString(); - } - - /// - /// Generate a nonce - /// - /// Nonce string - public virtual string GenerateNonce() - { - // Just a simple implementation of a random number between 123400 and 9999999 - return random.Next(123400, 9999999).ToString(); - } - - /// - /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case. - /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth - /// - /// The value to Url encode - /// Returns a Url encoded string - protected string UrlEncode(string value) - { - StringBuilder result = new StringBuilder(); - - foreach (char symbol in value) - { - if (UnreservedChars.IndexOf(symbol) != -1) - { - result.Append(symbol); - } - else - { - result.Append('%' + string.Format("{0:X2}", (int)symbol)); - } - } - - return result.ToString(); - } - - /// - /// Normalizes the request parameters according to the spec - /// - /// The list of parameters already sorted - /// a string representing the normalized parameters - protected string NormalizeRequestParameters(IList parameters) - { - StringBuilder sb = new StringBuilder(); - QueryParameter p = null; - for (int i = 0; i < parameters.Count; i++) - { - p = parameters[i]; - sb.AppendFormat("{0}={1}", p.Name, p.Value); - - if (i < parameters.Count - 1) - { - sb.Append("&"); - } - } - - return sb.ToString(); - } - - /// - /// Helper function to compute a hash value - /// - /// The hashing algorithm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function - /// The data to hash - /// a Base64 string of the hash value - private string ComputeHash(HashAlgorithm hashAlgorithm, string data) - { - if (hashAlgorithm == null) - { - throw new ArgumentNullException("hashAlgorithm"); - } - - if (string.IsNullOrEmpty(data)) - { - throw new ArgumentNullException("data"); - } - - byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data); - byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer); - - return Convert.ToBase64String(hashBytes); - } - - /// - /// Internal function to cut out all non oauth query string parameters (all parameters not beginning with "oauth_") - /// - /// The query string part of the Url - /// A list of QueryParameter each containing the parameter name and value - private List GetQueryParameters(string parameters) - { - if (parameters.StartsWith("?")) - { - parameters = parameters.Remove(0, 1); - } - - List result = new List(); - - if (!string.IsNullOrEmpty(parameters)) - { - string[] p = parameters.Split('&'); - foreach (string s in p) - { - if (!string.IsNullOrEmpty(s) && !s.StartsWith(OAuthParameterPrefix)) - { - if (s.IndexOf('=') > -1) - { - string[] temp = s.Split('='); - result.Add(new QueryParameter(temp[0], temp[1])); - } - else - { - result.Add(new QueryParameter(s, string.Empty)); - } - } - } - } - - return result; - } - - /// - /// Provides an internal structure to sort the query parameter - /// - protected class QueryParameter - { - private string name = null; - private string value = null; - - public QueryParameter(string name, string value) - { - this.name = name; - this.value = value; - } - - public string Name - { - get { return name; } - } - - public string Value - { - get { return value; } - } - } - - /// - /// Comparer class used to perform the sorting of the query parameters - /// - protected class QueryParameterComparer : IComparer - { - #region IComparer Members - - public int Compare(QueryParameter x, QueryParameter y) - { - if (x.Name == y.Name) - { - return string.Compare(x.Value, y.Value); - } - else - { - return string.Compare(x.Name, y.Name); - } - } - - #endregion - } - } -} diff --git a/Bynder/Api/Impl/Oauth/OAuthMessageHandler.cs b/Bynder/Api/Impl/Oauth/OAuthMessageHandler.cs deleted file mode 100644 index 1624d2a..0000000 --- a/Bynder/Api/Impl/Oauth/OAuthMessageHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -namespace Bynder.Api.Impl.Oauth -{ - /// - /// Class to sign requests. - /// Implementation from https://code.msdn.microsoft.com/Extending-HttpClient-with-8739f267/view/SourceCode - /// - internal class OAuthMessageHandler : DelegatingHandler - { - /// - /// Credentials to sign request - /// - private Credentials _credentials; - - /// - /// Oauth logic to generate the oauth header. - /// - private OAuthBase _oauthBase = new OAuthBase(); - - /// - /// Creates a new handler to sign requests using OAuth 1.0 - /// - /// credentials to sign requests - /// next handler - public OAuthMessageHandler(Credentials credentials, HttpMessageHandler innerHandler) - : base(innerHandler) - { - _credentials = credentials; - } - - /// - /// Adds Authorization header to the request - /// - /// HTTP request to sign - /// Cancellation token - /// Task with - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - // Compute OAuth header - string normalizedUri; - string normalizedParameters; - string authHeader; - - string signature = _oauthBase.GenerateSignature( - request.RequestUri, - _credentials.CONSUMER_KEY, - _credentials.CONSUMER_SECRET, - _credentials.ACCESS_TOKEN, - _credentials.ACCESS_TOKEN_SECRET, - request.Method.Method, - _oauthBase.GenerateTimeStamp(), - _oauthBase.GenerateNonce(), - out normalizedUri, - out normalizedParameters, - out authHeader); - - request.Headers.Authorization = new AuthenticationHeaderValue("OAuth", authHeader); - return base.SendAsync(request, cancellationToken); - } - } -} diff --git a/Bynder/Api/Impl/Oauth/OauthRequestSender.cs b/Bynder/Api/Impl/Oauth/OauthRequestSender.cs deleted file mode 100644 index 85ce727..0000000 --- a/Bynder/Api/Impl/Oauth/OauthRequestSender.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Specialized; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Web; -using Bynder.Api.Queries; -using Newtonsoft.Json; - -namespace Bynder.Api.Impl.Oauth -{ - /// - /// Implementation for - /// - internal class OauthRequestSender : IOauthRequestSender - { - /// - /// Base Url where API requests will be sent - /// - private readonly string _baseUrl; - - /// - /// Credentials used to generate oauth header - /// - private Credentials _credentials; - - /// - /// Query decoder to get parameters from query objects - /// - private QueryDecoder _queryDecoder = new QueryDecoder(); - - /// - /// HttpClient to send the API requests. - /// - private HttpClient _httpClient; - - /// - /// Creates a new instance of the class - /// - /// Credentials need to authenticate request - /// base Url where to send API requests - public OauthRequestSender(Credentials credentials, string baseUrl) - { - _credentials = credentials; - _baseUrl = baseUrl; - _httpClient = new HttpClient(new OAuthMessageHandler(credentials, new HttpClientHandler())); - _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; - } - - /// - /// Disposes httpClient instance - /// - public void Dispose() - { - _httpClient.Dispose(); - } - - /// - /// Check - /// - /// Check - /// Check - /// Check - public Task SendRequestAsync(Request request) - { - var parameters = _queryDecoder.GetParameters(request.Query); - return BynderRestCallAsync(request.HTTPMethod, request.Uri, parameters, request.DeserializeResponse); - } - - /// - /// Gets response to an API call and deserialize response to object if needed - /// - /// Object type we want to deserialize to - /// HTTP Method to use (GET, POST,...) - /// Uri to be appended to to do the request - /// Parameters to be used for the request - /// if T is a string we maybe don't want to deserialize to JSON. If T is string and this parameter is false, response is not deserialized - /// Task with response as T - private async Task BynderRestCallAsync(HttpMethod method, string uri, NameValueCollection requestParams, bool deserializeToJson) - { - var responseString = await BynderRestCallAsync(method, uri, requestParams).ConfigureAwait(false); - if (!string.IsNullOrEmpty(responseString)) - { - if (typeof(T) == typeof(string) - && !deserializeToJson) - { - // We can't return responseString directly. - return (T)Convert.ChangeType(responseString, typeof(T)); - } - - return JsonConvert.DeserializeObject(responseString); - } - - return default(T); - } - - /// - /// Gets reponse as raw string to an API call - /// - /// HTTP Method to use (GET, POST,...) - /// Uri to be appended to to do the request - /// Parameters to be used for the request - /// Task returning raw response string - private async Task BynderRestCallAsync(HttpMethod method, string uri, NameValueCollection requestParams) - { - Uri baseUri = new Uri(_baseUrl); - Uri uploadUri = new Uri(baseUri, uri); - - using (var httpResponseMessage = await SendOauthRequestAsync(method, uploadUri, requestParams).ConfigureAwait(false)) - { - var content = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - if (httpResponseMessage.IsSuccessStatusCode) - { - return content; - } - - throw new HttpRequestException(content); - } - } - - /// - /// Sends an API request adding appropriate Oauth headers. It returns a task with the HttpResponseMessage received. - /// - /// HTTP Method to use (GET, POST,...) - /// Uri to be appended to to do the request - /// Parameters to be used for the request - /// Task returning HttpResponseMessage received - private async Task SendOauthRequestAsync(HttpMethod method, Uri uri, NameValueCollection requestParams) - { - string parametersUrlEncoded = ConvertToUrlQuery(requestParams); - uri = new Uri(uri, $"?{parametersUrlEncoded}"); - using (HttpRequestMessage request = new HttpRequestMessage(method, uri)) - { - if (method == HttpMethod.Post) - { - request.Content = new StringContent(parametersUrlEncoded, Encoding.UTF8, "application/x-www-form-urlencoded"); - } - - // We need to await here so request is not disposed before the call finishes - return await _httpClient.SendAsync(request).ConfigureAwait(false); - } - } - - /// - /// Converts parameters collection to Url query string - /// - /// parameters collection - /// URL Encoded string - private string ConvertToUrlQuery(NameValueCollection parameters) - { - var encodedValues = parameters.AllKeys - .OrderBy(key => key) - .Select(key => $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(parameters[key])}"); - var queryUri = string.Join("&", encodedValues); - - // We need encoded values to be uppercase. - return Regex.Replace(queryUri, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper()); - } - } -} diff --git a/Bynder/Api/Queries/LoginQuery.cs b/Bynder/Api/Queries/LoginQuery.cs deleted file mode 100644 index e84ab47..0000000 --- a/Bynder/Api/Queries/LoginQuery.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -namespace Bynder.Api.Queries -{ - /// - /// Object used internally to login - /// - internal class LoginQuery - { - /// - /// Username - /// - [APIField("username")] - public string Username { get; set; } - - /// - /// Password - /// - [APIField("password")] - public string Password { get; set; } - - /// - /// Consumer id that corresponds to consumer key. - /// - [APIField("consumerId")] - public string ConsumerId { get; set; } - } -} diff --git a/Bynder/Api/Settings.cs b/Bynder/Api/Settings.cs deleted file mode 100644 index 0a45069..0000000 --- a/Bynder/Api/Settings.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using Newtonsoft.Json; -using System.IO; - -namespace Bynder.Api -{ - /// - /// Settings needed to configure - /// - public class Settings - { - /// - /// Oauth Consumer key - /// - [JsonProperty("consumer_key")] - public string CONSUMER_KEY { get; set; } - - /// - /// Oauth Consumer secret - /// - [JsonProperty("consumer_secret")] - public string CONSUMER_SECRET { get; set; } - - /// - /// Oauth token - /// - [JsonProperty("token")] - public string TOKEN { get; set; } - - /// - /// Oauth secret - /// - [JsonProperty("token_secret")] - public string TOKEN_SECRET { get; set; } - - /// - /// Bynder domain Url we want to communicate with - /// - [JsonProperty("api_base_url")] - public string URL { get; set; } - - /// - /// Create a using the given filepath - /// - /// JSON file path - /// instance - public static Settings FromJson(string filepath) - { - return JsonConvert.DeserializeObject(File.ReadAllText(filepath)); - } - } -} diff --git a/Bynder/Models/App.config b/Bynder/Models/App.config deleted file mode 100644 index 2d2a12d..0000000 --- a/Bynder/Models/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Bynder/Models/Bynder.Models.csproj b/Bynder/Models/Bynder.Models.csproj deleted file mode 100644 index 9e1d4e9..0000000 --- a/Bynder/Models/Bynder.Models.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - netstandard2.0 - 1.3.0 - 1.3.0 - Bynder - Bynder.Models - Copyright © Bynder - - - - - diff --git a/Bynder/Models/User.cs b/Bynder/Models/User.cs deleted file mode 100644 index 528cdd1..0000000 --- a/Bynder/Models/User.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using Newtonsoft.Json; - -namespace Bynder.Models -{ - /// - /// Model returned when success login through API /login - /// - public class User - { - /// - /// True if access was given to the username/password pair - /// - [JsonProperty("access")] - public bool Access { get; set; } - - /// - /// User id logged in - /// - [JsonProperty("userId")] - public string UserId { get; set; } - - /// - /// Token key returned by API - /// - [JsonProperty("tokenKey")] - public string TokenKey { get; set; } - - /// - /// Token secret returned by API - /// - [JsonProperty("tokenSecret")] - public string TokenSecret { get; set; } - } -} diff --git a/Bynder/Sample/ApiSample.cs b/Bynder/Sample/ApiSample.cs index a9a056b..6aad6d4 100644 --- a/Bynder/Sample/ApiSample.cs +++ b/Bynder/Sample/ApiSample.cs @@ -3,9 +3,13 @@ using System; using System.Configuration; -using Bynder.Api; -using Bynder.Api.Queries; -using Bynder.Sample.OauthUtils; +using Bynder.Sdk; +using Bynder.Sdk.Service; +using Bynder.Sample; +using Bynder.Sample.Utils; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Query.Collection; +using Bynder.Sdk.Settings; namespace Bynder.Sample { @@ -21,44 +25,33 @@ public class ApiSample /// arguments to main public static void Main(string[] args) { - using (IBynderApi bynderApi = BynderApiFactory.Create(Settings.FromJson("Config.json"))) + using(var waitForToken = new WaitForToken()) { - // We login using the browser. To do that we have to do the following: - // 1. Request temporary tokens - // 2. Get an authorized Url passing a callback where we will be redirected when login is successful. - // 3. Open a browser with the authorized Url and start listening to the callback url. - // 4. User logs in. - // 5. We request final access tokens. - bynderApi.GetRequestTokenAsync().Wait(); - string token = null; - - using (var listener = new OauthHttpListener("http://localhost:8891/")) + using (var listener = new UrlHttpListener("http://localhost:8888/", waitForToken)) { - var browser = new Browser(bynderApi.GetAuthorizeUrl("http://localhost:8891/")); - var waitForToken = listener.WaitForToken(); - waitForToken.Wait(); - token = waitForToken.Result; - } + using (var client = ClientFactory.Create(Configuration.FromJson("Config.json"))) + { + Browser.Launch(client.GetOAuthService().GetAuthorisationUrl("state example", "openid offline")); + waitForToken.WaitHandle.WaitOne(50000); - if (token != null) - { - bynderApi.GetAccessTokenAsync().Wait(); + if (waitForToken.Success) + { + client.GetOAuthService().GetAccessTokenAsync(waitForToken.Token, "openid offline").Wait(); - // Once the user has logged in we can start doing calls to asset bank manager to - // get media information or upload new files. - var assetBankManager = bynderApi.GetAssetBankManager(); + var mediaList = client.GetAssetService().GetMediaListAsync(new MediaQuery()).Result; - var mediaList = assetBankManager.RequestMediaListAsync(new MediaQuery - { - Page = 1, - Limit = 1 - }).Result; + foreach (var media in mediaList) + { + Console.WriteLine(media.Name); + } - if (mediaList.Count > 0) - { - var media = assetBankManager.RequestMediaInfoAsync(new MediaInformationQuery { MediaId = mediaList[0].Id, Versions = 0 }).Result; - assetBankManager.UploadFileAsync(new UploadQuery { Filepath = @"Image/bynder-logo.png", BrandId = media.BrandId }).Wait(); - Console.WriteLine("Uploaded file to Bynder"); + var collectionList = client.GetCollectionService().GetCollectionsAsync(new GetCollectionsQuery()).Result; + + foreach (var collection in collectionList) + { + Console.WriteLine(collection.Name); + } + } } } } diff --git a/Bynder/Sample/Bynder.Sample.csproj b/Bynder/Sample/Bynder.Sample.csproj index da8117e..4d4b1f6 100644 --- a/Bynder/Sample/Bynder.Sample.csproj +++ b/Bynder/Sample/Bynder.Sample.csproj @@ -2,8 +2,8 @@ Exe netcoreapp2.0 - 1.3.0 - 1.3.0 + 2.0.0 + 2.0.0 Bynder Bynder.Sample Copyright © Bynder @@ -12,7 +12,7 @@ - + diff --git a/Bynder/Sample/Config.json b/Bynder/Sample/Config.json index 7c25edc..f3524fa 100644 --- a/Bynder/Sample/Config.json +++ b/Bynder/Sample/Config.json @@ -1,5 +1,6 @@ { - "api_base_url": "", - "consumer_key": "", - "consumer_secret": "" + "base_url": "https://example.bynder.com", + "client_id": "your oauth app client id", + "client_secret": "your oauth app client secret", + "redirect_uri": "your oauth app redirect uri" } diff --git a/Bynder/Sample/OauthUtils/OauthHttpListener.cs b/Bynder/Sample/OauthUtils/OauthHttpListener.cs deleted file mode 100644 index e9bf69c..0000000 --- a/Bynder/Sample/OauthUtils/OauthHttpListener.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using System.Net; -using System.Threading.Tasks; - -namespace Bynder.Sample.OauthUtils -{ - /// - /// Util class to listen to specific Url. - /// It is used to login to Bynder through the browser when we specify the callback Url without the need to add any custom UI. - /// - public sealed class OauthHttpListener : IDisposable - { - /// - /// HttpListener that listens to Url passed in the constructor - /// - private readonly HttpListener _listener; - - /// - /// Creates a new OauthHttpListener that will listen to the Url and will notify waiting threads - /// when oauth login has completed - /// - /// Url we want to listen to - /// instance to pass token back and to notify waiting threads - public OauthHttpListener(string url) - { - _listener = new HttpListener(); - _listener.Prefixes.Add(url); - _listener.Start(); - } - - /// - /// Disposes de listener - /// - public void Dispose() - { - _listener.Close(); - } - - /// - /// Function that waits utils we start receiving data. - /// We keep listening until we find oauth_token parameter in the Url. - /// - /// async result - public async Task WaitForToken() - { - string token = null; - HttpListenerContext context = null; - - // Need to check if we are being requested with oauth_token. Because sometimes we - // would recieve the favicon call before, so we have to continue listening. - while (token == null) - { - context = await _listener.GetContextAsync(); - token = context.Request.QueryString.Get("oauth_token"); - } - - // Obtain a response object. - HttpListenerResponse response = context.Response; - - // Construct a response. - response.RedirectLocation = @"http://www.getbynder.com/en/"; - response.StatusCode = (int)HttpStatusCode.Redirect; - response.Close(); - - return token; - } - } -} diff --git a/Bynder/Sample/OauthUtils/Browser.cs b/Bynder/Sample/Utils/Browser.cs similarity index 57% rename from Bynder/Sample/OauthUtils/Browser.cs rename to Bynder/Sample/Utils/Browser.cs index b1f1351..b34ac3c 100644 --- a/Bynder/Sample/OauthUtils/Browser.cs +++ b/Bynder/Sample/Utils/Browser.cs @@ -1,27 +1,26 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using System; +using System; using System.Diagnostics; using System.Runtime.InteropServices; -namespace Bynder.Sample.OauthUtils +namespace Bynder.Sample.Utils { /// - /// Class to open the Browser with a specific Url. + /// Helper class to launch a Browser in different platforms. /// - public sealed class Browser + public class Browser { /// - /// Creates an instance of browser that will open the default browser pointing to the Url passed. + /// Launch a browser using the specified URL. /// - /// Url we want to open the browser with - /// token to notify if possible, if the user closes the browser - public Browser(string url) + /// URL. + public static void Launch(string url) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); // Works ok on windows } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { diff --git a/Bynder/Sample/Utils/UrlHttpListener.cs b/Bynder/Sample/Utils/UrlHttpListener.cs new file mode 100644 index 0000000..e9ae49d --- /dev/null +++ b/Bynder/Sample/Utils/UrlHttpListener.cs @@ -0,0 +1,96 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Net; + +namespace Bynder.Sample.Utils +{ + /// + /// Helper class to be able to catch the redirect response. + /// + public class UrlHttpListener : IDisposable + { + /// + /// HttpListener that listens to URL passed in the constructor. + /// + private readonly HttpListener _listener; + + /// + /// We use this class to notify waiting threads that a call with oauth_token is done + /// to the URL we are listening to. + /// + private readonly WaitForToken _waitForTokenHandle; + + /// + /// Creates a new OAuthHttpListener that will listen to the URL and will notify waiting threads + /// when oauth login has completed + /// + /// URL we want to listen to + /// instance to pass token back and to notify waiting threads + public UrlHttpListener(string url, WaitForToken waitForTokenHandle) + { + _listener = new HttpListener(); + _listener.Prefixes.Add(url); + _waitForTokenHandle = waitForTokenHandle; + _listener.Start(); + _listener.BeginGetContext(new AsyncCallback(BeginContextCallback), _listener); + } + + /// + /// Disposes the listener + /// + public void Dispose() + { + _listener.Close(); + } + + /// + /// Function callback that is called when we start receiving data. + /// We keep listening until we find oauth_token parameter in the URL. + /// + /// async result + private void BeginContextCallback(IAsyncResult result) + { + HttpListener listener = (HttpListener)result.AsyncState; + + // Call EndGetContext to complete the asynchronous operation. + HttpListenerContext context; + try + { + context = listener.EndGetContext(result); + } + catch (ObjectDisposedException) + { + // When Close this method is called with disposed object. Just swallow the exception + return; + } + + HttpListenerRequest request = context.Request; + + // Need to check if we are being requested with oauth_token. Because sometimes we + // would recieve the favicon call before, so we have to continue listening. + if (!request.RawUrl.Contains("code")) + { + _listener.BeginGetContext(new AsyncCallback(BeginContextCallback), _listener); + return; + } + + var token = request.QueryString["code"]; + _waitForTokenHandle.Success = token != null; + if (token != null) + { + _waitForTokenHandle.Token = token; + } + + // Obtain a response object. + HttpListenerResponse response = context.Response; + + // Construct a response. + response.StatusCode = (int)HttpStatusCode.Redirect; + response.Close(); + + _waitForTokenHandle.WaitHandle.Set(); + } + } +} diff --git a/Bynder/Sample/OauthUtils/WaitForToken.cs b/Bynder/Sample/Utils/WaitForToken.cs similarity index 90% rename from Bynder/Sample/OauthUtils/WaitForToken.cs rename to Bynder/Sample/Utils/WaitForToken.cs index c990f42..80f8fe2 100644 --- a/Bynder/Sample/OauthUtils/WaitForToken.cs +++ b/Bynder/Sample/Utils/WaitForToken.cs @@ -4,7 +4,7 @@ using System; using System.Threading; -namespace Bynder.Sample.OauthUtils +namespace Bynder.Sample.Utils { /// /// Class to notify waiting threads when oauth login proccess ended. @@ -17,7 +17,7 @@ public sealed class WaitForToken : IDisposable public bool Success { get; set; } = false; /// - /// Token passed in the Url when login is successful + /// Token passed in the URL when login is successful. /// public string Token { get; set; } diff --git a/Bynder/Models/Converter/BooleanJsonConverter.cs b/Bynder/Sdk/Api/Converters/BooleanJsonConverter.cs similarity index 94% rename from Bynder/Models/Converter/BooleanJsonConverter.cs rename to Bynder/Sdk/Api/Converters/BooleanJsonConverter.cs index d9726f9..cdde87e 100644 --- a/Bynder/Models/Converter/BooleanJsonConverter.cs +++ b/Bynder/Sdk/Api/Converters/BooleanJsonConverter.cs @@ -4,13 +4,13 @@ using System; using Newtonsoft.Json; -namespace Bynder.Models.Converter +namespace Bynder.Sdk.Api.Converters { /// /// Class used during Json deserialization to convert 0/1 to boolean members. We need it because the default bool converter only works if /// value to convert is "true"/"false". /// - internal class BooleanJsonConverter : JsonConverter + internal class BooleanJsonConverter : Newtonsoft.Json.JsonConverter { /// /// Checks if this converter can convert to a specific type. diff --git a/Bynder/Api/Converters/DateTimeOffsetConverter.cs b/Bynder/Sdk/Api/Converters/DateTimeOffsetConverter.cs similarity index 97% rename from Bynder/Api/Converters/DateTimeOffsetConverter.cs rename to Bynder/Sdk/Api/Converters/DateTimeOffsetConverter.cs index 3e506b3..3f9d824 100644 --- a/Bynder/Api/Converters/DateTimeOffsetConverter.cs +++ b/Bynder/Sdk/Api/Converters/DateTimeOffsetConverter.cs @@ -3,7 +3,7 @@ using System; -namespace Bynder.Api.Converters +namespace Bynder.Sdk.Api.Converters { /// /// Class to convert DateTime to a string formated as followed yyyy-MM-ddTHH:mm:ssZ diff --git a/Bynder/Api/Converters/ITypeToStringConverter.cs b/Bynder/Sdk/Api/Converters/ITypeToStringConverter.cs similarity index 96% rename from Bynder/Api/Converters/ITypeToStringConverter.cs rename to Bynder/Sdk/Api/Converters/ITypeToStringConverter.cs index edc0289..63b6b83 100644 --- a/Bynder/Api/Converters/ITypeToStringConverter.cs +++ b/Bynder/Sdk/Api/Converters/ITypeToStringConverter.cs @@ -3,7 +3,7 @@ using System; -namespace Bynder.Api.Converters +namespace Bynder.Sdk.Api.Converters { /// /// Interface for type converters used to decode specific diff --git a/Bynder/Api/Converters/JsonConverter.cs b/Bynder/Sdk/Api/Converters/JsonConverter.cs similarity index 96% rename from Bynder/Api/Converters/JsonConverter.cs rename to Bynder/Sdk/Api/Converters/JsonConverter.cs index 4113109..04a474d 100644 --- a/Bynder/Api/Converters/JsonConverter.cs +++ b/Bynder/Sdk/Api/Converters/JsonConverter.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; -namespace Bynder.Api.Converters +namespace Bynder.Sdk.Api.Converters { /// /// Class to convert objects to their Json string representation diff --git a/Bynder/Api/Converters/ListConverter.cs b/Bynder/Sdk/Api/Converters/ListConverter.cs similarity index 97% rename from Bynder/Api/Converters/ListConverter.cs rename to Bynder/Sdk/Api/Converters/ListConverter.cs index 55f37d1..1d50e88 100644 --- a/Bynder/Api/Converters/ListConverter.cs +++ b/Bynder/Sdk/Api/Converters/ListConverter.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Bynder.Api.Converters +namespace Bynder.Sdk.Api.Converters { /// /// Class used to convert IEnumerable{string} to string. diff --git a/Bynder/Api/Converters/LowerCaseEnumConverter.cs b/Bynder/Sdk/Api/Converters/LowerCaseEnumConverter.cs similarity index 97% rename from Bynder/Api/Converters/LowerCaseEnumConverter.cs rename to Bynder/Sdk/Api/Converters/LowerCaseEnumConverter.cs index adce29f..24d4a25 100644 --- a/Bynder/Api/Converters/LowerCaseEnumConverter.cs +++ b/Bynder/Sdk/Api/Converters/LowerCaseEnumConverter.cs @@ -3,7 +3,7 @@ using System; -namespace Bynder.Api.Converters +namespace Bynder.Sdk.Api.Converters { /// /// Class used to convert Enums or nullable Enums to their enumerator's name lowered case string. diff --git a/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs b/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs new file mode 100644 index 0000000..076388d --- /dev/null +++ b/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs @@ -0,0 +1,166 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Bynder.Sdk.Model; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Service; +using Newtonsoft.Json; +using Bynder.Sdk.Query.Decoder; +using Bynder.Sdk.Query; +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Api.RequestSender +{ + /// + /// Implementation of interface. + /// + internal class ApiRequestSender : IApiRequestSender + { + private readonly Configuration _configuration; + private readonly QueryDecoder _queryDecoder = new QueryDecoder(); + private readonly ICredentials _credentials; + private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + private IHttpRequestSender _httpSender; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration. + /// Credentials to use in authorized requests and to refresh tokens + /// HTTP instance to send API requests + internal ApiRequestSender(Configuration configuration, ICredentials credentials, IHttpRequestSender httpSender) + { + _configuration = configuration; + _httpSender = httpSender; + _credentials = credentials; + } + + /// + /// Create an instance of given the specified configuration and credentials. + /// + /// The instance. + /// Configuration. + /// Credentials. + public static IApiRequestSender Create(Configuration configuration, ICredentials credentials) + { + return new ApiRequestSender(configuration, credentials, new HttpRequestSender()); + } + + /// + /// Releases all resources used by the object. + /// + /// Call when you are finished using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the + /// so the garbage collector can reclaim the memory that the + /// was occupying. + public void Dispose() + { + _httpSender.Dispose(); + } + + /// + /// Check . + /// + /// Check . + /// Check . + /// Check . + public async Task SendRequestAsync(Requests.Request request) + { + if (request.Authenticated && !_credentials.AreValid()) + { + await _semaphore.WaitAsync().ConfigureAwait(false); + if (!_credentials.AreValid()) + { + try + { + await RefreshToken().ConfigureAwait(false); + } + finally + { + _semaphore.Release(); + } + } + } + + var httpRequest = CreateHttpRequest(request); + var responseString = await _httpSender.SendHttpRequest(httpRequest).ConfigureAwait(false); + if (!string.IsNullOrEmpty(responseString)) + { + return JsonConvert.DeserializeObject(responseString); + } + + return default(T); + } + + private HttpRequestMessage CreateHttpRequest(Requests.Request request) + { + var parameters = _queryDecoder.GetParameters(request.Query); + var httpRequestMessage = HttpRequestMessageFactory.Create( + _configuration.BaseUrl.ToString(), + request.HTTPMethod, + parameters, + request.Path); + + if (request.Authenticated) + { + httpRequestMessage.Headers.Authorization = + new AuthenticationHeaderValue(_credentials.TokenType, _credentials.AccessToken); + } + + return httpRequestMessage; + } + + private async Task RefreshToken() + { + TokenQuery query = new TokenQuery + { + ClientId = _configuration.ClientId, + ClientSecret = _configuration.ClientSecret, + RefreshToken = _credentials.RefreshToken, + GrantType = "refresh_token" + }; + + var request = new OAuthRequest + { + Authenticated = false, + Query = query, + Path = $"/oauth2/token", + HTTPMethod = HttpMethod.Post + }; + + var newToken = await SendRequestAsync(request).ConfigureAwait(false); + + _credentials.Update(newToken); + } + + private static class HttpRequestMessageFactory + { + internal static HttpRequestMessage Create( + string baseUrl, HttpMethod method, IDictionary requestParams, string urlPath) + { + var builder = new UriBuilder(baseUrl); + builder.Path = urlPath; + + if (HttpMethod.Get == method) + { + builder.Query = Utils.Url.ConvertToQuery(requestParams); + } + + HttpRequestMessage requestMessage = new HttpRequestMessage(method, builder.ToString()); + if (HttpMethod.Post == method) + { + requestMessage.Content = new FormUrlEncodedContent(requestParams); + } + + return requestMessage; + } + } + } +} diff --git a/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs b/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs new file mode 100644 index 0000000..efafd6c --- /dev/null +++ b/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs @@ -0,0 +1,43 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Net.Http; +using System.Threading.Tasks; + +namespace Bynder.Sdk.Api.RequestSender +{ + /// + /// HTTP request sender. Used to send HTTP Requests. + /// It eases unit testing of other components checking that correct requests are being sent. + /// + internal class HttpRequestSender : IHttpRequestSender + { + private readonly HttpClient _httpClient = new HttpClient(); + + /// + /// Sends the HTTP request and returns the content as string. + /// + /// The HTTP request response. + /// HTTP request. + public async Task SendHttpRequest(HttpRequestMessage httpRequest) + { + var response = await _httpClient.SendAsync(httpRequest).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } + + /// + /// Releases all resources used by the object. + /// + /// Call when you are finished using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the + /// so the garbage collector can reclaim the memory that the + /// was occupying. + public void Dispose() + { + _httpClient.Dispose(); + } + } +} diff --git a/Bynder/Sdk/Api/RequestSender/IApiRequestSender.cs b/Bynder/Sdk/Api/RequestSender/IApiRequestSender.cs new file mode 100644 index 0000000..4b289cf --- /dev/null +++ b/Bynder/Sdk/Api/RequestSender/IApiRequestSender.cs @@ -0,0 +1,25 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + + +using System; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; + +namespace Bynder.Sdk.Api.RequestSender +{ + /// + /// API request sender interface. All requests to Bynder are done using + /// this request sender. + /// + internal interface IApiRequestSender : IDisposable + { + /// + /// Sends the request async. + /// + /// The deserialized response. + /// Request. + /// Type we want to deserialize response to. + Task SendRequestAsync(Request request); + } +} diff --git a/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs b/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs new file mode 100644 index 0000000..7b52963 --- /dev/null +++ b/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs @@ -0,0 +1,23 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Bynder.Sdk.Api.RequestSender +{ + /// + /// HTTP request sender. Used to send HTTP Requests. + /// It eases unit testing of other components checking that correct requests are being sent. + /// + internal interface IHttpRequestSender : IDisposable + { + /// + /// Sends the HTTP request and returns the content as string. + /// + /// The HTTP request response. + /// HTTP request. + Task SendHttpRequest(HttpRequestMessage httpRequest); + } +} diff --git a/Bynder/Sdk/Api/Requests/ApiRequest.cs b/Bynder/Sdk/Api/Requests/ApiRequest.cs new file mode 100644 index 0000000..d83f23b --- /dev/null +++ b/Bynder/Sdk/Api/Requests/ApiRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +namespace Bynder.Sdk.Api.Requests +{ + /// + /// API request. + /// + /// Type to which the response will be deserialized + internal class ApiRequest : Request + { + } +} diff --git a/Bynder/Sdk/Api/Requests/OAuthRequest.cs b/Bynder/Sdk/Api/Requests/OAuthRequest.cs new file mode 100644 index 0000000..8ac502d --- /dev/null +++ b/Bynder/Sdk/Api/Requests/OAuthRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +namespace Bynder.Sdk.Api.Requests +{ + /// + /// OAuth request. + /// + /// Type to which the response will be deserialized + internal class OAuthRequest : Request + { + } +} diff --git a/Bynder/Api/Queries/Request.cs b/Bynder/Sdk/Api/Requests/Request.cs similarity index 60% rename from Bynder/Api/Queries/Request.cs rename to Bynder/Sdk/Api/Requests/Request.cs index bc14aa8..2666939 100644 --- a/Bynder/Api/Queries/Request.cs +++ b/Bynder/Sdk/Api/Requests/Request.cs @@ -3,14 +3,13 @@ using System.Net.Http; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Api.Requests { /// - /// Class that contains all information to do an API request to Bynder - /// through + /// API Request representation. /// - /// type we want the response to be deserialized to - internal class Request + /// Type to which the response will be deserialized + internal abstract class Request { /// /// Object with information about the API parameters to send. @@ -18,15 +17,21 @@ internal class Request public object Query { get; set; } /// - /// Uri of the endpoint to call + /// Path of the endpoint to call. /// - public string Uri { get; set; } + public string Path { get; set; } /// - /// HttpMethod to use + /// HttpMethod to use. /// public HttpMethod HTTPMethod { get; set; } + /// + /// Gets or sets a value indicating whether this is authenticated. + /// + /// true if authenticated; otherwise, false. + public bool Authenticated { get; set; } = true; + /// /// True if we want to deserialize response to . /// However if is a string and this property has value false, we might want to get the raw string response without diff --git a/Bynder/Api/Bynder.Api.csproj b/Bynder/Sdk/Bynder.Sdk.csproj similarity index 77% rename from Bynder/Api/Bynder.Api.csproj rename to Bynder/Sdk/Bynder.Sdk.csproj index 29b20eb..f279f63 100644 --- a/Bynder/Api/Bynder.Api.csproj +++ b/Bynder/Sdk/Bynder.Sdk.csproj @@ -1,19 +1,16 @@ netstandard2.0 - 1.3.0 - 1.3.0 + 2.0.0 + 2.0.0 Bynder - Bynder.Api + Bynder.Sdk Copyright © Bynder - - - <_Parameter1>Bynder.Test diff --git a/Bynder/Api/Bynder.Api.nuspec b/Bynder/Sdk/Bynder.Sdk.nuspec similarity index 96% rename from Bynder/Api/Bynder.Api.nuspec rename to Bynder/Sdk/Bynder.Sdk.nuspec index ec2fc33..fe8c1c6 100644 --- a/Bynder/Api/Bynder.Api.nuspec +++ b/Bynder/Sdk/Bynder.Sdk.nuspec @@ -2,7 +2,7 @@ Bynder.Sdk - 1.3.0 + 2.0.0 Bynder.Sdk Bynder B.V. Bynder B.V. diff --git a/Bynder/Api/BynderUploadException.cs b/Bynder/Sdk/Exceptions/BynderUploadException.cs similarity index 95% rename from Bynder/Api/BynderUploadException.cs rename to Bynder/Sdk/Exceptions/BynderUploadException.cs index 3010cb4..698999f 100644 --- a/Bynder/Api/BynderUploadException.cs +++ b/Bynder/Sdk/Exceptions/BynderUploadException.cs @@ -3,7 +3,7 @@ using System; -namespace Bynder.Api +namespace Bynder.Sdk.Exceptions { /// /// Exception thrown when Upload does not finish diff --git a/Bynder/Sdk/Exceptions/InvalidConfigurationException.cs b/Bynder/Sdk/Exceptions/InvalidConfigurationException.cs new file mode 100644 index 0000000..0638265 --- /dev/null +++ b/Bynder/Sdk/Exceptions/InvalidConfigurationException.cs @@ -0,0 +1,23 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; + +namespace Bynder.Sdk.Exceptions +{ + /// + /// Invalid configuration exception. Raised when an invalid configuration + /// is passed to . + /// + public class InvalidConfigurationException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Reason of the exception. + public InvalidConfigurationException(string message) + : base(message) + { + } + } +} diff --git a/Bynder/Sdk/Exceptions/MissingTokenException.cs b/Bynder/Sdk/Exceptions/MissingTokenException.cs new file mode 100644 index 0000000..b089bfe --- /dev/null +++ b/Bynder/Sdk/Exceptions/MissingTokenException.cs @@ -0,0 +1,23 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; + +namespace Bynder.Sdk.Exceptions +{ + /// + /// Missing token exception. Exception raised when trying to use + /// without a valid token + /// + public class MissingTokenException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Exception reason + public MissingTokenException(string message) + : base(message) + { + } + } +} diff --git a/Bynder/Models/AssetType.cs b/Bynder/Sdk/Model/AssetType.cs similarity index 95% rename from Bynder/Models/AssetType.cs rename to Bynder/Sdk/Model/AssetType.cs index cc2be18..e527a9f 100644 --- a/Bynder/Models/AssetType.cs +++ b/Bynder/Sdk/Model/AssetType.cs @@ -1,7 +1,7 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Possible types of assets diff --git a/Bynder/Models/Brand.cs b/Bynder/Sdk/Model/Brand.cs similarity index 96% rename from Bynder/Models/Brand.cs rename to Bynder/Sdk/Model/Brand.cs index e1536ae..e1de3f5 100644 --- a/Bynder/Models/Brand.cs +++ b/Bynder/Sdk/Model/Brand.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Brand information. diff --git a/Bynder/Models/Collection.cs b/Bynder/Sdk/Model/Collection.cs similarity index 98% rename from Bynder/Models/Collection.cs rename to Bynder/Sdk/Model/Collection.cs index 9ebca2a..8eaffe3 100644 --- a/Bynder/Models/Collection.cs +++ b/Bynder/Sdk/Model/Collection.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model describing a collection diff --git a/Bynder/Models/CollectionCover.cs b/Bynder/Sdk/Model/CollectionCover.cs similarity index 96% rename from Bynder/Models/CollectionCover.cs rename to Bynder/Sdk/Model/CollectionCover.cs index d0e8708..47cbc7b 100644 --- a/Bynder/Models/CollectionCover.cs +++ b/Bynder/Sdk/Model/CollectionCover.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model describing the cover of a collection diff --git a/Bynder/Models/DownloadFileUrl.cs b/Bynder/Sdk/Model/DownloadFileUrl.cs similarity index 94% rename from Bynder/Models/DownloadFileUrl.cs rename to Bynder/Sdk/Model/DownloadFileUrl.cs index 43b7bf9..c5c6803 100644 --- a/Bynder/Models/DownloadFileUrl.cs +++ b/Bynder/Sdk/Model/DownloadFileUrl.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model returned by media_id/download/item_id diff --git a/Bynder/Models/FinalizeResponse.cs b/Bynder/Sdk/Model/FinalizeResponse.cs similarity index 94% rename from Bynder/Models/FinalizeResponse.cs rename to Bynder/Sdk/Model/FinalizeResponse.cs index 46ba869..1b27117 100644 --- a/Bynder/Models/FinalizeResponse.cs +++ b/Bynder/Sdk/Model/FinalizeResponse.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model returned by /finalize for uploads diff --git a/Bynder/Models/Media.cs b/Bynder/Sdk/Model/Media.cs similarity index 98% rename from Bynder/Models/Media.cs rename to Bynder/Sdk/Model/Media.cs index 0f7eae0..d8220dd 100644 --- a/Bynder/Models/Media.cs +++ b/Bynder/Sdk/Model/Media.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using Newtonsoft.Json; -using Bynder.Models.Converter; using Newtonsoft.Json.Linq; +using Bynder.Sdk.Api.Converters; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Media model returned by API /media diff --git a/Bynder/Models/MediaItem.cs b/Bynder/Sdk/Model/MediaItem.cs similarity index 96% rename from Bynder/Models/MediaItem.cs rename to Bynder/Sdk/Model/MediaItem.cs index 0716216..be46cc7 100644 --- a/Bynder/Models/MediaItem.cs +++ b/Bynder/Sdk/Model/MediaItem.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.ComponentModel; -using Bynder.Models.Converter; +using Bynder.Sdk.Api.Converters; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Media Item specific information received from of IAssetBankManager diff --git a/Bynder/Models/MetaProperty.cs b/Bynder/Sdk/Model/Metaproperty.cs similarity index 97% rename from Bynder/Models/MetaProperty.cs rename to Bynder/Sdk/Model/Metaproperty.cs index 3028027..ce101e2 100644 --- a/Bynder/Models/MetaProperty.cs +++ b/Bynder/Sdk/Model/Metaproperty.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Models.Converter; +using Bynder.Sdk.Api.Converters; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model to represent a metaproperty diff --git a/Bynder/Models/MetapropertyOption.cs b/Bynder/Sdk/Model/MetapropertyOption.cs similarity index 96% rename from Bynder/Models/MetapropertyOption.cs rename to Bynder/Sdk/Model/MetapropertyOption.cs index ae25448..c4f17e3 100644 --- a/Bynder/Models/MetapropertyOption.cs +++ b/Bynder/Sdk/Model/MetapropertyOption.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Models.Converter; +using Bynder.Sdk.Api.Converters; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model to represent metaproperty options diff --git a/Bynder/Models/MultipartParameters.cs b/Bynder/Sdk/Model/MultipartParameters.cs similarity index 98% rename from Bynder/Models/MultipartParameters.cs rename to Bynder/Sdk/Model/MultipartParameters.cs index 924933a..583624b 100644 --- a/Bynder/Models/MultipartParameters.cs +++ b/Bynder/Sdk/Model/MultipartParameters.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Parameters needed to upload a part to Amazon. This model is only and should diff --git a/Bynder/Models/PollStatus.cs b/Bynder/Sdk/Model/PollStatus.cs similarity index 96% rename from Bynder/Models/PollStatus.cs rename to Bynder/Sdk/Model/PollStatus.cs index 3ac2318..e3ceb30 100644 --- a/Bynder/Models/PollStatus.cs +++ b/Bynder/Sdk/Model/PollStatus.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model for the/poll response of the API. This model is only and should diff --git a/Bynder/Models/S3File.cs b/Bynder/Sdk/Model/S3File.cs similarity index 96% rename from Bynder/Models/S3File.cs rename to Bynder/Sdk/Model/S3File.cs index a4d3310..25f9fef 100644 --- a/Bynder/Models/S3File.cs +++ b/Bynder/Sdk/Model/S3File.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model for the /upload/init/ response of the API. This model is only and should diff --git a/Bynder/Models/Thumbnails.cs b/Bynder/Sdk/Model/Thumbnails.cs similarity index 97% rename from Bynder/Models/Thumbnails.cs rename to Bynder/Sdk/Model/Thumbnails.cs index 8145b8b..7eab247 100644 --- a/Bynder/Models/Thumbnails.cs +++ b/Bynder/Sdk/Model/Thumbnails.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model to represent the thumbnails of a media asset diff --git a/Bynder/Sdk/Model/Token.cs b/Bynder/Sdk/Model/Token.cs new file mode 100644 index 0000000..08542de --- /dev/null +++ b/Bynder/Sdk/Model/Token.cs @@ -0,0 +1,77 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Newtonsoft.Json; + +namespace Bynder.Sdk.Model +{ + /// + /// Token Model returned when OAuth2 flow finishes or when + /// the access token is refreshed + /// + public class Token + { + /// + /// Gets the access token. Used to do authenticated requests to the API. + /// + /// The access token. + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + /// + /// Gets the number of milliseconds it will take for the access token to expire. + /// + /// Number of milliseconds + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + /// + /// Gets or sets the id of the token. + /// + /// The id of the token. + [JsonProperty("id_token")] + public string TokenId { get; set; } + + /// + /// Gets or sets the type of the token. + /// + /// The type of the token. + [JsonProperty("token_type")] + public string TokenType { get; set; } + + /// + /// Gets or sets the scope. + /// + /// The scope. + [JsonProperty("scope")] + public string Scope { get; set; } + + /// + /// Gets the refresh token. Used to get new access tokens + /// + /// The refresh token. + [JsonProperty("refresh_token")] + public string RefreshToken { get; set; } + + private DateTimeOffset AccessTokenExpiration; + + /// + /// Gets the access token expiration. + /// + /// The access token expiration. + public DateTimeOffset GetAccessTokenExpiration() + { + return AccessTokenExpiration; + } + + /// + /// Sets the access token expiration according to its ExpiresIn value. + /// + /// The access token expiration. + public void SetAccessTokenExpiration() + { + AccessTokenExpiration = DateTimeOffset.Now.AddSeconds(ExpiresIn); + } + } +} \ No newline at end of file diff --git a/Bynder/Models/UploadRequest.cs b/Bynder/Sdk/Model/UploadRequest.cs similarity index 97% rename from Bynder/Models/UploadRequest.cs rename to Bynder/Sdk/Model/UploadRequest.cs index 2f7cd0c..929881b 100644 --- a/Bynder/Models/UploadRequest.cs +++ b/Bynder/Sdk/Model/UploadRequest.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; -namespace Bynder.Models +namespace Bynder.Sdk.Model { /// /// Model to get the information to start an upload /upload/init. This model is only and should diff --git a/Bynder/Api/Queries/DownloadMediaQuery.cs b/Bynder/Sdk/Query/Asset/DownloadMediaQuery.cs similarity index 94% rename from Bynder/Api/Queries/DownloadMediaQuery.cs rename to Bynder/Sdk/Query/Asset/DownloadMediaQuery.cs index 34fae76..c90183f 100644 --- a/Bynder/Api/Queries/DownloadMediaQuery.cs +++ b/Bynder/Sdk/Query/Asset/DownloadMediaQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Asset { /// /// Query to specify the media from which we want the Url diff --git a/Bynder/Api/Queries/MediaInformationQuery.cs b/Bynder/Sdk/Query/Asset/MediaInformationQuery.cs similarity index 86% rename from Bynder/Api/Queries/MediaInformationQuery.cs rename to Bynder/Sdk/Query/Asset/MediaInformationQuery.cs index 97fc1a7..9d5e12a 100644 --- a/Bynder/Api/Queries/MediaInformationQuery.cs +++ b/Bynder/Sdk/Query/Asset/MediaInformationQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Asset { /// /// Query used to get media information including media items @@ -16,7 +18,7 @@ public class MediaInformationQuery /// /// This property has to be set to 1 so API returns media items /// - [APIField("versions")] + [ApiField("versions")] public int Versions { get; set; } = 1; } } diff --git a/Bynder/Api/Queries/MediaQuery.cs b/Bynder/Sdk/Query/Asset/MediaQuery.cs similarity index 78% rename from Bynder/Api/Queries/MediaQuery.cs rename to Bynder/Sdk/Query/Asset/MediaQuery.cs index 11c3c51..aa5ffff 100644 --- a/Bynder/Api/Queries/MediaQuery.cs +++ b/Bynder/Sdk/Query/Asset/MediaQuery.cs @@ -2,10 +2,11 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Api.Converters; -using Bynder.Models; +using Bynder.Sdk.Model; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Asset { /// /// Query to filter media results @@ -15,43 +16,43 @@ public class MediaQuery /// /// Brand id. Specify this property if you want only media for specific brand. /// - [APIField("brandId")] + [ApiField("brandId")] public string BrandId { get; set; } /// /// SubBrand id. Specify this property if you want only media for specific subBrand. /// - [APIField("subBrandId")] + [ApiField("subBrandId")] public string SubBrandId { get; set; } /// /// Limit of results per request. Max 1000. Default 50. /// - [APIField("limit")] + [ApiField("limit")] public int? Limit { get; set; } /// /// Page to be retrieved. /// - [APIField("page")] + [ApiField("page")] public int? Page { get; set; } /// /// Keyword that the asset has to have to appear in the results /// - [APIField("keyword")] + [ApiField("keyword")] public string Keyword { get; set; } /// /// The type of the asset /// - [APIField("type", Converter = typeof(LowerCaseEnumConverter))] + [ApiField("type", Converter = typeof(LowerCaseEnumConverter))] public AssetType? Type { get; set; } /// /// Metaproperty option ids that the asset has to have /// - [APIField("propertyOptionId", Converter = typeof(ListConverter))] + [ApiField("propertyOptionId", Converter = typeof(ListConverter))] public IList PropertyOptionId { get; set; } = new List(); } } diff --git a/Bynder/Api/Queries/ModifyMediaQuery.cs b/Bynder/Sdk/Query/Asset/ModifyMediaQuery.cs similarity index 85% rename from Bynder/Api/Queries/ModifyMediaQuery.cs rename to Bynder/Sdk/Query/Asset/ModifyMediaQuery.cs index 6932775..8c753a6 100644 --- a/Bynder/Api/Queries/ModifyMediaQuery.cs +++ b/Bynder/Sdk/Query/Asset/ModifyMediaQuery.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using Bynder.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Asset { /// /// Query to modify a media with @@ -26,31 +26,31 @@ public ModifyMediaQuery(string mediaId) /// /// Name of the media /// - [APIField("name")] + [ApiField("name")] public string Name { get; set; } /// /// Description of the media /// - [APIField("description")] + [ApiField("description")] public string Description { get; set; } /// /// Copyright information for the media /// - [APIField("copyright")] + [ApiField("copyright")] public string Copyright { get; set; } /// /// Indicates if the media is archived /// - [APIField("archive")] + [ApiField("archive")] public bool Archive { get; set; } /// /// Indicates if the media is public /// - [APIField("isPublic")] + [ApiField("isPublic")] public bool IsPublic { get; set; } } } diff --git a/Bynder/Api/Queries/Collections/AddMediaQuery.cs b/Bynder/Sdk/Query/Collection/AddMediaQuery.cs similarity index 86% rename from Bynder/Api/Queries/Collections/AddMediaQuery.cs rename to Bynder/Sdk/Query/Collection/AddMediaQuery.cs index 2e6700a..da08111 100644 --- a/Bynder/Api/Queries/Collections/AddMediaQuery.cs +++ b/Bynder/Sdk/Query/Collection/AddMediaQuery.cs @@ -2,9 +2,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Api.Converters; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries.Collections +namespace Bynder.Sdk.Query.Collection { /// /// Query used to add media to a collection @@ -30,7 +31,7 @@ public AddMediaQuery(string collectionId, IList mediaIds) /// /// List with the Ids of the media /// - [APIField("data", Converter = typeof(JsonConverter))] + [ApiField("data", Converter = typeof(JsonConverter))] public IList MediaIds { get; private set; } } } diff --git a/Bynder/Api/Queries/Collections/CreateCollectionQuery.cs b/Bynder/Sdk/Query/Collection/CreateCollectionQuery.cs similarity index 85% rename from Bynder/Api/Queries/Collections/CreateCollectionQuery.cs rename to Bynder/Sdk/Query/Collection/CreateCollectionQuery.cs index 335e40c..8dec03c 100644 --- a/Bynder/Api/Queries/Collections/CreateCollectionQuery.cs +++ b/Bynder/Sdk/Query/Collection/CreateCollectionQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries.Collections +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Collection { /// /// Query to create collections @@ -20,13 +22,13 @@ public CreateCollectionQuery(string name) /// /// Name of collection /// - [APIField("name")] + [ApiField("name")] public string Name { get; private set; } /// /// Description of collection /// - [APIField("description")] + [ApiField("description")] public string Description { get; set; } } } diff --git a/Bynder/Api/Queries/Collections/GetCollectionsQuery.cs b/Bynder/Sdk/Query/Collection/GetCollectionsQuery.cs similarity index 81% rename from Bynder/Api/Queries/Collections/GetCollectionsQuery.cs rename to Bynder/Sdk/Query/Collection/GetCollectionsQuery.cs index 9c88ce1..188461c 100644 --- a/Bynder/Api/Queries/Collections/GetCollectionsQuery.cs +++ b/Bynder/Sdk/Query/Collection/GetCollectionsQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries.Collections +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Collection { /// /// Query to retrieve a list of collections @@ -11,13 +13,13 @@ public class GetCollectionsQuery /// /// Limit of results per request. Max 1000. Default 50. /// - [APIField("limit")] + [ApiField("limit")] public int? Limit { get; set; } /// /// Page to be retrieved. /// - [APIField("page")] + [ApiField("page")] public int? Page { get; set; } } } diff --git a/Bynder/Api/Queries/Collections/GetMediaQuery.cs b/Bynder/Sdk/Query/Collection/GetMediaQuery.cs similarity index 92% rename from Bynder/Api/Queries/Collections/GetMediaQuery.cs rename to Bynder/Sdk/Query/Collection/GetMediaQuery.cs index 04c992b..5a62806 100644 --- a/Bynder/Api/Queries/Collections/GetMediaQuery.cs +++ b/Bynder/Sdk/Query/Collection/GetMediaQuery.cs @@ -1,9 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using Bynder.Models; +using Bynder.Sdk.Model; -namespace Bynder.Api.Queries.Collections +namespace Bynder.Sdk.Query.Collection { /// /// Query to get media from a collection diff --git a/Bynder/Api/Queries/Collections/RemoveMediaQuery.cs b/Bynder/Sdk/Query/Collection/RemoveMediaQuery.cs similarity index 85% rename from Bynder/Api/Queries/Collections/RemoveMediaQuery.cs rename to Bynder/Sdk/Query/Collection/RemoveMediaQuery.cs index cf2295e..9006396 100644 --- a/Bynder/Api/Queries/Collections/RemoveMediaQuery.cs +++ b/Bynder/Sdk/Query/Collection/RemoveMediaQuery.cs @@ -2,10 +2,11 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Api.Converters; -using Bynder.Models; +using Bynder.Sdk.Model; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries.Collections +namespace Bynder.Sdk.Query.Collection { /// /// Query to remove media from a collection @@ -31,7 +32,7 @@ public RemoveMediaQuery(string collectionId, IList mediaIds) /// /// List with the Ids of the media /// - [APIField("deleteIds", Converter = typeof(ListConverter))] + [ApiField("deleteIds", Converter = typeof(ListConverter))] public IList MediaIds { get; private set; } } } diff --git a/Bynder/Api/Queries/Collections/ShareQuery.cs b/Bynder/Sdk/Query/Collection/ShareQuery.cs similarity index 81% rename from Bynder/Api/Queries/Collections/ShareQuery.cs rename to Bynder/Sdk/Query/Collection/ShareQuery.cs index 081712c..31141b6 100644 --- a/Bynder/Api/Queries/Collections/ShareQuery.cs +++ b/Bynder/Sdk/Query/Collection/ShareQuery.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; -using Bynder.Api.Converters; -using Bynder.Api.Queries.Collections.PermissionOptions; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries.Collections +namespace Bynder.Sdk.Query.Collection { /// /// Query to share a collection with @@ -34,43 +34,43 @@ public ShareQuery(string collectionId, IList recipients, SharingPermissi /// /// Email addresses of the /// - [APIField("recipients", Converter = typeof(ListConverter))] + [ApiField("recipients", Converter = typeof(ListConverter))] public IList Recipients { get; private set; } /// /// Permission rights of the recipients /// - [APIField("collectionOptions", Converter = typeof(LowerCaseEnumConverter))] + [ApiField("collectionOptions", Converter = typeof(LowerCaseEnumConverter))] public SharingPermission Permission { get; private set; } /// /// Flags if the recipients should login to view the collection /// - [APIField("loginRequired")] + [ApiField("loginRequired")] public bool LoginRequired { get; set; } /// /// Sharing start date /// - [APIField("dateStart", Converter = typeof(DateTimeOffsetConverter))] + [ApiField("dateStart", Converter = typeof(DateTimeOffsetConverter))] public DateTimeOffset? DateStart { get; set; } /// /// Sharing end date /// - [APIField("dateEnd", Converter = typeof(DateTimeOffsetConverter))] + [ApiField("dateEnd", Converter = typeof(DateTimeOffsetConverter))] public DateTimeOffset? DateEnd { get; set; } /// /// Flags if the recipients should recieve an email /// - [APIField("sendMail")] + [ApiField("sendMail")] public bool SendMail { get; set; } /// /// Message to include in the email that will be sent /// - [APIField("message")] + [ApiField("message")] public string Message { get; set; } } } diff --git a/Bynder/Api/Queries/Collections/PermissionOptions/SharingPermission.cs b/Bynder/Sdk/Query/Collection/SharingPermission.cs similarity index 89% rename from Bynder/Api/Queries/Collections/PermissionOptions/SharingPermission.cs rename to Bynder/Sdk/Query/Collection/SharingPermission.cs index dd8c0df..3b2f553 100644 --- a/Bynder/Api/Queries/Collections/PermissionOptions/SharingPermission.cs +++ b/Bynder/Sdk/Query/Collection/SharingPermission.cs @@ -1,7 +1,7 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries.Collections.PermissionOptions +namespace Bynder.Sdk.Query.Collection { /// /// Enum for Permission Options for sharing a collection diff --git a/Bynder/Api/Queries/APIField.cs b/Bynder/Sdk/Query/Decoder/ApiField.cs similarity index 89% rename from Bynder/Api/Queries/APIField.cs rename to Bynder/Sdk/Query/Decoder/ApiField.cs index 189fe65..d9e8ae1 100644 --- a/Bynder/Api/Queries/APIField.cs +++ b/Bynder/Sdk/Query/Decoder/ApiField.cs @@ -3,20 +3,20 @@ using System; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Decoder { /// /// Class to be used as attributes for properties in queries to specify /// if property needs to be send as parameter to the API /// [AttributeUsage(AttributeTargets.Property)] - public class APIField : Attribute + public class ApiField : Attribute { /// /// Initializes a new instance of the class /// /// Name of the field in the API documentation - public APIField(string name) + public ApiField(string name) { ApiName = name; } diff --git a/Bynder/Sdk/Query/Decoder/IParameterDecoder.cs b/Bynder/Sdk/Query/Decoder/IParameterDecoder.cs new file mode 100644 index 0000000..3b90f15 --- /dev/null +++ b/Bynder/Sdk/Query/Decoder/IParameterDecoder.cs @@ -0,0 +1,27 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; + +namespace Bynder.Sdk.Query.Decoder +{ + /// + /// Decoder interface to use in queries to convert from one specific type to string. + /// + public interface IParameterDecoder + { + /// + /// Checks if the converter can convert a specific type. + /// + /// Type to convert from + /// true if it can convert the type + bool CanConvert(Type typeToConvert); + + /// + /// Converts the value to string. + /// + /// value to be converted + /// converted string value + string Convert(object value); + } +} \ No newline at end of file diff --git a/Bynder/Api/Queries/QueryDecoder.cs b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs similarity index 76% rename from Bynder/Api/Queries/QueryDecoder.cs rename to Bynder/Sdk/Query/Decoder/QueryDecoder.cs index 1f5b854..7ffd5d4 100644 --- a/Bynder/Api/Queries/QueryDecoder.cs +++ b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs @@ -2,31 +2,30 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; -using Bynder.Api.Converters; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Decoder { /// - /// Class that gets a query and extracts all properties with the - /// values and returns them in a . + /// Decodes query object to a dictionary of parameters. /// internal class QueryDecoder { /// /// Given a query object it gets the parameters. The parameters are basically the properties - /// of the object query that have attributes. + /// of the object query that have attributes. /// /// query object /// collection with name and value to send to the API - public NameValueCollection GetParameters(object query) + public IDictionary GetParameters(object query) { - NameValueCollection parameters = new NameValueCollection(); + var parameters = new Dictionary(); if (query != null) { - foreach (var propertyInfo in query.GetType().GetProperties()) + foreach (var propertyInfo in query.GetType().GetTypeInfo().GetProperties()) { ConvertProperty(propertyInfo, query, parameters); } @@ -36,18 +35,18 @@ public NameValueCollection GetParameters(object query) } /// - /// Function called for each property in a query object. It extracts the different properties - /// with attribute and, if needed, calls appropiate converter to convert property value to string + /// Function called for each property in a query object. It extracts the different properties + /// with attribute and, if needed, calls appropiate converter to convert property value to string. /// /// property type information /// query object /// collection to add the converted values - private void ConvertProperty(PropertyInfo propertyInfo, object query, NameValueCollection collection) + private void ConvertProperty(PropertyInfo propertyInfo, object query, IDictionary collection) { var attributes = propertyInfo.GetCustomAttributes(true); foreach (var attribute in attributes) { - APIField nameAttr = attribute as APIField; + ApiField nameAttr = attribute as ApiField; if (nameAttr != null) { object value = propertyInfo.GetValue(query); @@ -60,7 +59,7 @@ private void ConvertProperty(PropertyInfo propertyInfo, object query, NameValueC } } - // No need to continue. Only one APIField attribute per property + // No need to continue. Only one ApiField attribute per property return; } } @@ -68,19 +67,19 @@ private void ConvertProperty(PropertyInfo propertyInfo, object query, NameValueC /// /// Function called to convert property values to string. If no converter is - /// specified, then .ToString is called + /// specified, then .ToString is called. /// /// API field attribute /// property type information /// current value /// converted value - private string ConvertPropertyValue(APIField apiField, Type propertyType, object value) + private string ConvertPropertyValue(ApiField apiField, Type propertyType, object value) { string convertedValue = null; bool isConverted = false; if (apiField.Converter != null) { - ITypeToStringConverter converter = Activator.CreateInstance(apiField.Converter) as ITypeToStringConverter; + IParameterDecoder converter = Activator.CreateInstance(apiField.Converter) as IParameterDecoder; if (converter != null && converter.CanConvert(propertyType)) diff --git a/Bynder/Sdk/Query/TokenQuery.cs b/Bynder/Sdk/Query/TokenQuery.cs new file mode 100644 index 0000000..86e6549 --- /dev/null +++ b/Bynder/Sdk/Query/TokenQuery.cs @@ -0,0 +1,62 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query +{ + /// + /// Query to retrieve and refresh tokens. + /// + public class TokenQuery + { + /// + /// Gets or sets the client identifier. + /// + /// The client identifier. + [ApiField("client_id")] + public string ClientId { get; set; } + + /// + /// Gets or sets the client secret. + /// + /// The client secret. + [ApiField("client_secret")] + public string ClientSecret { get; set; } + + /// + /// Gets or sets the redirect URI. + /// + /// The redirect URI. + [ApiField("redirect_uri")] + public string RedirectUri { get; set; } + + /// + /// Gets or sets the type of the grant. + /// + /// The type of the grant. + [ApiField("grant_type")] + public string GrantType { get; set; } + + /// + /// Gets or sets the code. + /// + /// The code. + [ApiField("code")] + public string Code { get; set; } + + /// + /// Gets or sets the scopes. + /// + /// The scopes. + [ApiField("scope")] + public string Scopes { get; set; } + + /// + /// Gets or sets the refresh token. + /// + /// The refresh token. + [ApiField("refresh_token")] + public string RefreshToken { get; set; } + } +} diff --git a/Bynder/Api/Queries/FinalizeUploadQuery.cs b/Bynder/Sdk/Query/Upload/FinalizeUploadQuery.cs similarity index 82% rename from Bynder/Api/Queries/FinalizeUploadQuery.cs rename to Bynder/Sdk/Query/Upload/FinalizeUploadQuery.cs index a19228c..a26e7d0 100644 --- a/Bynder/Api/Queries/FinalizeUploadQuery.cs +++ b/Bynder/Sdk/Query/Upload/FinalizeUploadQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Upload { /// /// Finalize upload information. This model should only be used by UploadFile @@ -11,19 +13,19 @@ internal class FinalizeUploadQuery /// /// Target id /// - [APIField("targetid")] + [ApiField("targetid")] public string TargetId { get; set; } /// /// S3 filename /// - [APIField("s3_filename")] + [ApiField("s3_filename")] public string S3Filename { get; set; } /// /// Number of chunks /// - [APIField("chunks")] + [ApiField("chunks")] public string Chunks { get; set; } /// diff --git a/Bynder/Api/Queries/PollQuery.cs b/Bynder/Sdk/Query/Upload/PollQuery.cs similarity index 77% rename from Bynder/Api/Queries/PollQuery.cs rename to Bynder/Sdk/Query/Upload/PollQuery.cs index b24a772..8f9283c 100644 --- a/Bynder/Api/Queries/PollQuery.cs +++ b/Bynder/Sdk/Query/Upload/PollQuery.cs @@ -2,9 +2,10 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using Bynder.Api.Converters; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Upload { /// /// Class to pass information to poll if asset has finished converting. This @@ -15,7 +16,7 @@ internal class PollQuery /// /// Items we want to query the status. /// - [APIField("items", Converter = typeof(ListConverter))] + [ApiField("items", Converter = typeof(ListConverter))] public IList Items { get; set; } = new List(); } } diff --git a/Bynder/Api/Queries/RegisterChunkQuery.cs b/Bynder/Sdk/Query/Upload/RegisterChunkQuery.cs similarity index 83% rename from Bynder/Api/Queries/RegisterChunkQuery.cs rename to Bynder/Sdk/Query/Upload/RegisterChunkQuery.cs index 13b1588..0da9b62 100644 --- a/Bynder/Api/Queries/RegisterChunkQuery.cs +++ b/Bynder/Sdk/Query/Upload/RegisterChunkQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Upload { /// /// Class used to represent RegisterChunk response. This @@ -12,19 +14,19 @@ internal class RegisterChunkQuery /// /// Target id /// - [APIField("targetid")] + [ApiField("targetid")] public string TargetId { get; set; } /// /// S3 filename /// - [APIField("filename")] + [ApiField("filename")] public string S3Filename { get; set; } /// /// Chunk number /// - [APIField("chunkNumber")] + [ApiField("chunkNumber")] public string ChunkNumber { get; set; } /// diff --git a/Bynder/Api/Queries/RequestUploadQuery.cs b/Bynder/Sdk/Query/Upload/RequestUploadQuery.cs similarity index 83% rename from Bynder/Api/Queries/RequestUploadQuery.cs rename to Bynder/Sdk/Query/Upload/RequestUploadQuery.cs index 09721f6..7726bb7 100644 --- a/Bynder/Api/Queries/RequestUploadQuery.cs +++ b/Bynder/Sdk/Query/Upload/RequestUploadQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Upload { /// /// Query with the information to init an upload. This @@ -12,7 +14,7 @@ internal class RequestUploadQuery /// /// Filename of the file we want to initialize the upload /// - [APIField("filename")] + [ApiField("filename")] public string Filename { get; set; } } } diff --git a/Bynder/Api/Queries/SaveMediaQuery.cs b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs similarity index 88% rename from Bynder/Api/Queries/SaveMediaQuery.cs rename to Bynder/Sdk/Query/Upload/SaveMediaQuery.cs index e993a18..6559e61 100644 --- a/Bynder/Api/Queries/SaveMediaQuery.cs +++ b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs @@ -1,7 +1,9 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Upload { /// /// Query that has the information to save media. @@ -12,13 +14,13 @@ internal class SaveMediaQuery /// /// Brand id we want to save media to /// - [APIField("brandid")] + [ApiField("brandid")] public string BrandId { get; set; } /// /// Name of the asset /// - [APIField("name")] + [ApiField("name")] public string Filename { get; set; } /// diff --git a/Bynder/Api/Queries/UploadQuery.cs b/Bynder/Sdk/Query/Upload/UploadQuery.cs similarity index 96% rename from Bynder/Api/Queries/UploadQuery.cs rename to Bynder/Sdk/Query/Upload/UploadQuery.cs index 967ee63..ba27503 100644 --- a/Bynder/Api/Queries/UploadQuery.cs +++ b/Bynder/Sdk/Query/Upload/UploadQuery.cs @@ -1,7 +1,7 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. -namespace Bynder.Api.Queries +namespace Bynder.Sdk.Query.Upload { /// /// Query with the information to upload a file diff --git a/Bynder/Sdk/Service/Asset/AssetService.cs b/Bynder/Sdk/Service/Asset/AssetService.cs new file mode 100644 index 0000000..c177ed7 --- /dev/null +++ b/Bynder/Sdk/Service/Asset/AssetService.cs @@ -0,0 +1,161 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Service.Upload; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Query.Upload; + +namespace Bynder.Sdk.Service.Asset +{ + /// + /// Implementation of + /// + internal class AssetService : IAssetService + { + /// + /// Request sender to communicate with the Bynder API + /// + private IApiRequestSender _requestSender; + + /// + /// Instance to upload file to Bynder + /// + private FileUploader _uploader; + + /// + /// Initializes a new instance of the class + /// + /// instance to communicate with the Bynder API + public AssetService(IApiRequestSender requestSender) + { + _requestSender = requestSender; + _uploader = FileUploader.Create(_requestSender); + } + + /// + /// Check for more information + /// + /// Check for more information + public Task> GetBrandsAsync() + { + var request = new ApiRequest> + { + Path = "/api/v4/brands/", + HTTPMethod = HttpMethod.Get + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + public Task> GetMetapropertiesAsync() + { + var request = new ApiRequest> + { + Path = "/api/v4/metaproperties/", + HTTPMethod = HttpMethod.Get + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task> GetMediaListAsync(MediaQuery query) + { + var request = new ApiRequest> + { + Path = "/api/v4/media/", + HTTPMethod = HttpMethod.Get, + Query = query + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public async Task GetDownloadFileUrlAsync(DownloadMediaQuery query) + { + string path = string.Empty; + if (query.MediaItemId == null) + { + path = $"/api/v4/media/{query.MediaId}/download/"; + } + else + { + path = $"/api/v4/media/{query.MediaId}/download/{query.MediaItemId}/"; + } + + var request = new ApiRequest + { + Path = path, + HTTPMethod = HttpMethod.Get + }; + + var downloadFileInformation = await _requestSender.SendRequestAsync(request).ConfigureAwait(false); + return downloadFileInformation.S3File; + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task UploadFileAsync(UploadQuery query) + { + return _uploader.UploadFile(query); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task GetMediaInfoAsync(MediaInformationQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/media/{query.MediaId}/", + HTTPMethod = HttpMethod.Get, + Query = query + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task ModifyMediaAsync(ModifyMediaQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/media/{query.MediaId}/", + HTTPMethod = HttpMethod.Post, + Query = query, + DeserializeResponse = false + }; + + return _requestSender.SendRequestAsync(request); + } + } +} diff --git a/Bynder/Api/IAssetBankManager.cs b/Bynder/Sdk/Service/Asset/IAssetService.cs similarity index 92% rename from Bynder/Api/IAssetBankManager.cs rename to Bynder/Sdk/Service/Asset/IAssetService.cs index c14e567..6fc02e6 100644 --- a/Bynder/Api/IAssetBankManager.cs +++ b/Bynder/Sdk/Service/Asset/IAssetService.cs @@ -4,15 +4,16 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Bynder.Api.Queries; -using Bynder.Models; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Query.Upload; -namespace Bynder.Api +namespace Bynder.Sdk.Service.Asset { /// /// Interface to represent operations that can be done to the Bynder Asset Bank /// - public interface IAssetBankManager + public interface IAssetService { /// /// Gets Brands Async @@ -44,7 +45,7 @@ public interface IAssetBankManager /// Information about the media we want to get the information of. /// Task with the Media information /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task RequestMediaInfoAsync(MediaInformationQuery query); + Task GetMediaInfoAsync(MediaInformationQuery query); /// /// Gets a list of media using query information. The media information is not complete, for example @@ -53,7 +54,7 @@ public interface IAssetBankManager /// information to correctly filter/paginate media /// Task with List of media. /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task> RequestMediaListAsync(MediaQuery query); + Task> GetMediaListAsync(MediaQuery query); /// /// Uploads a file async. diff --git a/Bynder/Sdk/Service/BynderClient.cs b/Bynder/Sdk/Service/BynderClient.cs new file mode 100644 index 0000000..8db9ac5 --- /dev/null +++ b/Bynder/Sdk/Service/BynderClient.cs @@ -0,0 +1,107 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Exceptions; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Service.Asset; +using Bynder.Sdk.Service.Collection; +using Bynder.Sdk.Service.OAuth; +using Bynder.Sdk.Settings.Validators; +using Bynder.Sdk.Model; +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Service +{ + /// + /// Client implementation of . + /// + internal class BynderClient : IBynderClient + { + private readonly Configuration _configuration; + private readonly IApiRequestSender _requestSender; + private ICredentials _credentials; + private IOAuthService _oauthService; + private IAssetService _assetService; + private ICollectionService _collectionService; + + /// + /// Initializes a new instance of the class. + /// + /// Client configuration. + public BynderClient(Configuration configuration) + { + new ConfigurationValidator().Validate(configuration); + _configuration = configuration; + _credentials = new Credentials(configuration.Token); + _requestSender = ApiRequestSender.Create(_configuration, _credentials); + } + + /// + /// Check + /// + public event EventHandler OnCredentialsChanged + { + add + { + _credentials.OnCredentialsChanged += value; + } + + remove + { + _credentials.OnCredentialsChanged -= value; + } + } + + /// + /// Releases all resource used by the object. + /// + /// Call when you are finished using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the so + /// the garbage collector can reclaim the memory that the was occupying. + public void Dispose() + { + _requestSender.Dispose(); + } + + /// + /// Check + /// + /// Check + public IAssetService GetAssetService() + { + if (!_credentials.AreValid() && !_credentials.CanRefresh) + { + throw new MissingTokenException("Access token expired and refresh token is missing. " + + "Either pass a not expited access token through configuration or login through OAuth2"); + } + + return _assetService ?? (_assetService = new AssetService(_requestSender)); + } + + /// + /// Check + /// + /// Check + public ICollectionService GetCollectionService() + { + if (!_credentials.AreValid() && !_credentials.CanRefresh) + { + throw new MissingTokenException("Access token expired and refresh token is missing. " + + "Either pass a not expited access token through configuration or login through OAuth2"); + } + + return _collectionService ?? (_collectionService = new CollectionService(_requestSender)); + } + + /// + /// Check + /// + /// Check + public IOAuthService GetOAuthService() + { + return _oauthService ?? (_oauthService = new OAuthService(_configuration, _credentials, _requestSender)); + } + } +} \ No newline at end of file diff --git a/Bynder/Sdk/Service/ClientFactory.cs b/Bynder/Sdk/Service/ClientFactory.cs new file mode 100644 index 0000000..d5887e5 --- /dev/null +++ b/Bynder/Sdk/Service/ClientFactory.cs @@ -0,0 +1,23 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Service +{ + /// + /// Factory to create Bynder Client. + /// + public static class ClientFactory + { + /// + /// Creates the client to be used to communicate with Bynder. + /// + /// Bynder Client. + /// Configuration used to create the client. + public static IBynderClient Create(Configuration configuration) + { + return new BynderClient(configuration); + } + } +} \ No newline at end of file diff --git a/Bynder/Sdk/Service/Collection/CollectionService.cs b/Bynder/Sdk/Service/Collection/CollectionService.cs new file mode 100644 index 0000000..145b539 --- /dev/null +++ b/Bynder/Sdk/Service/Collection/CollectionService.cs @@ -0,0 +1,169 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Query.Collection; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; + +namespace Bynder.Sdk.Service.Collection +{ + /// + /// Implementation of + /// + internal class CollectionService : ICollectionService + { + /// + /// Request sender to communicate with the Bynder API + /// + private IApiRequestSender _requestSender; + + /// + /// Initializes a new instance of the class + /// + /// instance to communicate with the Bynder API + public CollectionService(IApiRequestSender requestSender) + { + _requestSender = requestSender; + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task CreateCollectionAsync(CreateCollectionQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/", + HTTPMethod = HttpMethod.Post, + Query = query, + DeserializeResponse = false + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task DeleteCollectionAsync(string id) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/{id}/", + HTTPMethod = HttpMethod.Delete + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task GetCollectionAsync(string id) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/{id}/", + HTTPMethod = HttpMethod.Get + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task> GetCollectionsAsync(GetCollectionsQuery query) + { + var request = new ApiRequest> + { + Path = "/api/v4/collections/", + HTTPMethod = HttpMethod.Get, + Query = query + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task> GetMediaAsync(GetMediaQuery query) + { + var request = new ApiRequest> + { + Path = $"/api/v4/collections/{query.CollectionId}/media/", + HTTPMethod = HttpMethod.Get + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task AddMediaAsync(AddMediaQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/{query.CollectionId}/media/", + HTTPMethod = HttpMethod.Post, + Query = query, + DeserializeResponse = false + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task RemoveMediaAsync(RemoveMediaQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/{query.CollectionId}/media/", + HTTPMethod = HttpMethod.Delete, + Query = query + }; + + return _requestSender.SendRequestAsync(request); + } + + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + public Task ShareCollectionAsync(ShareQuery query) + { + var request = new ApiRequest + { + Path = $"/api/v4/collections/{query.CollectionId}/share/", + HTTPMethod = HttpMethod.Post, + Query = query, + DeserializeResponse = false + }; + + return _requestSender.SendRequestAsync(request); + } + } +} diff --git a/Bynder/Api/ICollectionsManager.cs b/Bynder/Sdk/Service/Collection/ICollectionService.cs similarity index 92% rename from Bynder/Api/ICollectionsManager.cs rename to Bynder/Sdk/Service/Collection/ICollectionService.cs index b39426b..52f97df 100644 --- a/Bynder/Api/ICollectionsManager.cs +++ b/Bynder/Sdk/Service/Collection/ICollectionService.cs @@ -3,22 +3,22 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Bynder.Api.Queries.Collections; -using Bynder.Models; +using Bynder.Sdk.Query.Collection; +using Bynder.Sdk.Model; -namespace Bynder.Api +namespace Bynder.Sdk.Service.Collection { /// /// Interface to represent operations that can be done to the Bynder Media Collections /// - public interface ICollectionsManager + public interface ICollectionService { /// /// Gets Collections Async /// /// information to correctly filter/paginate /// Task with a list of items - Task> GetCollectionsAsync(GetCollectionsQuery query); + Task> GetCollectionsAsync(GetCollectionsQuery query); /// /// Gets a specific colection @@ -26,7 +26,7 @@ public interface ICollectionsManager /// The uuid of the specific /// Task that contains the specific /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error - Task GetCollectionAsync(string id); + Task GetCollectionAsync(string id); /// /// Creates a new Collection diff --git a/Bynder/Sdk/Service/IBynderClient.cs b/Bynder/Sdk/Service/IBynderClient.cs new file mode 100644 index 0000000..18b877c --- /dev/null +++ b/Bynder/Sdk/Service/IBynderClient.cs @@ -0,0 +1,41 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Service.Asset; +using Bynder.Sdk.Service.Collection; +using Bynder.Sdk.Service.OAuth; +using Bynder.Sdk.Model; + +namespace Bynder.Sdk.Service +{ + /// + /// Bynder Client interface. + /// + public interface IBynderClient : IDisposable + { + /// + /// Occurs when credentials changed, and that happens every time + /// the access token is refreshed. + /// + event EventHandler OnCredentialsChanged; + + /// + /// Gets the asset service to interact with assets in your Bynder portal. + /// + /// The asset service. + IAssetService GetAssetService(); + + /// + /// Gets the collection service to interact with collections in your Bynder portal. + /// + /// The collection service. + ICollectionService GetCollectionService(); + + /// + /// Gets the OAuth service. + /// + /// The OAuth service. + IOAuthService GetOAuthService(); + } +} \ No newline at end of file diff --git a/Bynder/Sdk/Service/OAuth/IOAuthService.cs b/Bynder/Sdk/Service/OAuth/IOAuthService.cs new file mode 100644 index 0000000..4a60792 --- /dev/null +++ b/Bynder/Sdk/Service/OAuth/IOAuthService.cs @@ -0,0 +1,29 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace Bynder.Sdk.Service.OAuth +{ + /// + /// Oauth service interface. Service that handles OAuth logic. + /// + public interface IOAuthService + { + /// + /// Gets the authorisation URL. + /// + /// The authorisation URL. + /// State string to be checked to avoid CSRF. https://auth0.com/docs/protocols/oauth2/oauth-state + /// Scopes to request authorization for + string GetAuthorisationUrl(string state, string scopes); + + /// + /// Gets an access token using the code authorization grant. + /// + /// The task to get the access token and update the credentials with it. + /// Code received after the redirect + /// The authorization scopes + Task GetAccessTokenAsync(string code, string scopes); + } +} diff --git a/Bynder/Sdk/Service/OAuth/OAuthService.cs b/Bynder/Sdk/Service/OAuth/OAuthService.cs new file mode 100644 index 0000000..b79c284 --- /dev/null +++ b/Bynder/Sdk/Service/OAuth/OAuthService.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query; +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Service.OAuth +{ + internal class OAuthService : IOAuthService + { + private readonly Configuration _configuration; + private readonly ICredentials _credentials; + /// + /// Request sender to communicate with the Bynder API + /// + private IApiRequestSender _requestSender; + + /// + /// Initializes a new instance of the class + /// + /// instance to communicate with the Bynder API + public OAuthService(Configuration configuration, ICredentials credentials, IApiRequestSender requestSender) + { + _configuration = configuration; + _credentials = credentials; + _requestSender = requestSender; + } + + /// + /// Check . + /// + /// Check . + /// Check . + /// Check . + public string GetAuthorisationUrl(string state, string scopes) + { + if (string.IsNullOrEmpty(state)) + { + throw new ArgumentNullException(state); + } + + var authoriseParams = new Dictionary + { + { "client_id", _configuration.ClientId }, + { "redirect_uri", _configuration.RedirectUri }, + { "scope", scopes }, + { "response_type", "code" }, + { "state", state } + }; + + var builder = new UriBuilder(_configuration.BaseUrl); + builder.Path = "/v6/authentication/oauth2/auth"; + + builder.Query = Utils.Url.ConvertToQuery(authoriseParams); + + return builder.ToString(); + } + + /// + /// Check . + /// + /// Check . + /// Check . + /// Check . + public async Task GetAccessTokenAsync(string code, string scopes) + { + if (string.IsNullOrEmpty(code)) + { + throw new ArgumentNullException(code); + } + + TokenQuery query = new TokenQuery + { + ClientId = _configuration.ClientId, + ClientSecret = _configuration.ClientSecret, + RedirectUri = _configuration.RedirectUri, + GrantType = "authorization_code", + Code = code, + Scopes = scopes + }; + + var request = new OAuthRequest + { + Query = query, + Path = "/v6/authentication/oauth2/token", + HTTPMethod = HttpMethod.Post, + Authenticated = false + }; + + var token = await _requestSender.SendRequestAsync(request).ConfigureAwait(false); + token.SetAccessTokenExpiration(); + _credentials.Update(token); + } + } +} diff --git a/Bynder/Api/Impl/Upload/AmazonApi.cs b/Bynder/Sdk/Service/Upload/AmazonApi.cs similarity index 98% rename from Bynder/Api/Impl/Upload/AmazonApi.cs rename to Bynder/Sdk/Service/Upload/AmazonApi.cs index d5b9327..7c44dc1 100644 --- a/Bynder/Api/Impl/Upload/AmazonApi.cs +++ b/Bynder/Sdk/Service/Upload/AmazonApi.cs @@ -3,9 +3,9 @@ using System.Net.Http; using System.Threading.Tasks; -using Bynder.Models; +using Bynder.Sdk.Model; -namespace Bynder.Api.Impl.Upload +namespace Bynder.Sdk.Service.Upload { /// /// Implementation of diff --git a/Bynder/Api/Impl/Upload/FileUploader.cs b/Bynder/Sdk/Service/Upload/FileUploader.cs similarity index 91% rename from Bynder/Api/Impl/Upload/FileUploader.cs rename to Bynder/Sdk/Service/Upload/FileUploader.cs index 12bbb4f..e461d14 100644 --- a/Bynder/Api/Impl/Upload/FileUploader.cs +++ b/Bynder/Sdk/Service/Upload/FileUploader.cs @@ -5,11 +5,13 @@ using System.IO; using System.Net.Http; using System.Threading.Tasks; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Models; +using Bynder.Sdk.Exceptions; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query.Upload; -namespace Bynder.Api.Impl.Upload +namespace Bynder.Sdk.Service.Upload { /// /// Class used to upload files to Bynder @@ -34,7 +36,7 @@ internal class FileUploader /// /// Request sender used to call Bynder API. /// - private readonly IOauthRequestSender _requestSender; + private readonly IApiRequestSender _requestSender; /// /// Amazon API used to upload parts @@ -51,7 +53,7 @@ internal class FileUploader /// /// Request sender to communicate with Bynder API /// Amazon API to upload parts - public FileUploader(IOauthRequestSender requestSender, IAmazonApi amazonApi) + public FileUploader(IApiRequestSender requestSender, IAmazonApi amazonApi) { _requestSender = requestSender; _amazonApi = amazonApi; @@ -62,7 +64,7 @@ public FileUploader(IOauthRequestSender requestSender, IAmazonApi amazonApi) /// /// Request sender to communicate with Bynder API /// new instance - public static FileUploader Create(IOauthRequestSender requestSender) + public static FileUploader Create(IApiRequestSender requestSender) { return new FileUploader(requestSender, new AmazonApi()); } @@ -117,9 +119,9 @@ private async Task GetClosestS3EndpointAsync() { if (string.IsNullOrEmpty(_awsBucket)) { - var request = new Request + var request = new ApiRequest { - Uri = "/api/upload/endpoint", + Path = "/api/upload/endpoint", HTTPMethod = HttpMethod.Get }; @@ -139,19 +141,19 @@ private Task SaveMediaAsync(SaveMediaQuery query) { query.Filename = Path.GetFileName(query.Filename); - string uri = null; + string path = null; if (query.MediaId == null) { - uri = $"/api/v4/media/save/{query.ImportId}/"; + path = $"/api/v4/media/save/{query.ImportId}/"; } else { - uri = $"/api/v4/media/{query.MediaId}/save/{query.ImportId}/"; + path = $"/api/v4/media/{query.MediaId}/save/{query.ImportId}/"; } - var request = new Request + var request = new ApiRequest { - Uri = uri, + Path = path, HTTPMethod = HttpMethod.Post, Query = query, DeserializeResponse = false @@ -183,9 +185,9 @@ private Task PollStatusAsync(FinalizeResponse finalizeResponse) /// Task with poll status information private Task PollStatusAsync(PollQuery query) { - var request = new Request + var request = new ApiRequest { - Uri = "/api/v4/upload/poll/", + Path = "/api/v4/upload/poll/", HTTPMethod = HttpMethod.Get, Query = query }; @@ -249,9 +251,9 @@ private Task RegisterChunkAsync(RegisterChunkQuery query) { query.S3Filename = $"{query.S3Filename}/p{query.ChunkNumber}"; - var request = new Request + var request = new ApiRequest { - Uri = $"/api/v4/upload/{query.UploadId}/", + Path = $"/api/v4/upload/{query.UploadId}/", HTTPMethod = HttpMethod.Post, Query = query, DeserializeResponse = false @@ -284,9 +286,9 @@ private Task RegisterChunkAsync(UploadRequest uploadRequest, uint chunkNumber) /// Task containing information private Task RequestUploadInformationAsync(RequestUploadQuery query) { - var request = new Request + var request = new ApiRequest { - Uri = "/api/upload/init", + Path = "/api/upload/init", HTTPMethod = HttpMethod.Post, Query = query }; @@ -321,9 +323,9 @@ private Task FinalizeUploadAsync(FinalizeUploadQuery query) { query.S3Filename = $"{query.S3Filename}/p{query.Chunks}"; - var request = new Request + var request = new ApiRequest { - Uri = $"/api/v4/upload/{query.UploadId}/", + Path = $"/api/v4/upload/{query.UploadId}/", HTTPMethod = HttpMethod.Post, Query = query }; diff --git a/Bynder/Api/Impl/Upload/IAmazonApi.cs b/Bynder/Sdk/Service/Upload/IAmazonApi.cs similarity index 94% rename from Bynder/Api/Impl/Upload/IAmazonApi.cs rename to Bynder/Sdk/Service/Upload/IAmazonApi.cs index 95e2450..b26425a 100644 --- a/Bynder/Api/Impl/Upload/IAmazonApi.cs +++ b/Bynder/Sdk/Service/Upload/IAmazonApi.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Threading.Tasks; -using Bynder.Models; +using Bynder.Sdk.Model; -namespace Bynder.Api.Impl.Upload +namespace Bynder.Sdk.Service.Upload { /// /// Interface to upload file parts to Amazon diff --git a/Bynder/Sdk/Settings/Configuration.cs b/Bynder/Sdk/Settings/Configuration.cs new file mode 100644 index 0000000..9821cd7 --- /dev/null +++ b/Bynder/Sdk/Settings/Configuration.cs @@ -0,0 +1,57 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using Bynder.Sdk.Model; +using Newtonsoft.Json; +using System; +using System.IO; + +namespace Bynder.Sdk.Settings +{ + /// + /// Settings needed to configure + /// + public class Configuration + { + /// + /// Bynder domain Url we want to communicate with + /// + [JsonProperty("base_url")] + public Uri BaseUrl { get; set; } + + /// + /// OAuth Client id + /// + [JsonProperty("client_id")] + public string ClientId { get; set; } + + /// + /// OAuth Client secret + /// + [JsonProperty("client_secret")] + public string ClientSecret { get; set; } + + /// + /// Gets or sets the redirect URI. Optional: It is + /// only needed if trying to login through OAuth + /// + /// The redirect URI. + [JsonProperty("redirect_uri")] + public string RedirectUri { get; set; } + + /// + /// OAuth token + /// + public Token Token { get; set; } + + /// + /// Create a using the given filepath + /// + /// JSON file path + /// instance + public static Configuration FromJson(string filepath) + { + return JsonConvert.DeserializeObject(File.ReadAllText(filepath)); + } + } +} diff --git a/Bynder/Sdk/Settings/Credentials.cs b/Bynder/Sdk/Settings/Credentials.cs new file mode 100644 index 0000000..5f170aa --- /dev/null +++ b/Bynder/Sdk/Settings/Credentials.cs @@ -0,0 +1,107 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Net; +using Bynder.Sdk.Model; + +namespace Bynder.Sdk.Settings +{ + /// + /// Credentials implementation. + /// + internal class Credentials : ICredentials + { + private Token _token; + + /// + /// Initializes a new instance of the class. + /// + /// Token passed in the configuration. + public Credentials(Token token) + { + _token = token; + } + + /// + /// Check . + /// + public event EventHandler OnCredentialsChanged; + + /// + /// Check . + /// + /// Check . + public string AccessToken => _token?.AccessToken; + + /// + /// Check . + /// + /// Check . + public string RefreshToken => _token?.RefreshToken; + + /// + /// Check . + /// + /// Check . + public bool CanRefresh + { + get + { + return RefreshToken != null; + } + } + + /// + /// Check . + /// + /// Check . + public string TokenType => _token?.TokenType; + + /// + /// Gets or sets the token that will be used to authenticate API calls. + /// + /// The token. + private Token Token + { + get + { + return _token; + } + + set + { + if (value != _token) + { + _token = value; + OnCredentialsChanged?.Invoke(this, _token); + } + } + } + + /// + /// Check . + /// + /// Check . + public bool AreValid() + { + if (_token != null + && _token.AccessToken != null) + { + var limitExpiration = DateTimeOffset.UtcNow.AddSeconds(15); + return _token.GetAccessTokenExpiration() > limitExpiration; + } + + return false; + } + + /// + /// Check . + /// + /// Check . + public void Update(Token token) + { + Token = token; + } + } +} diff --git a/Bynder/Sdk/Settings/ICredentials.cs b/Bynder/Sdk/Settings/ICredentials.cs new file mode 100644 index 0000000..36a0598 --- /dev/null +++ b/Bynder/Sdk/Settings/ICredentials.cs @@ -0,0 +1,54 @@ +using System; +using Bynder.Sdk.Model; + +namespace Bynder.Sdk.Settings +{ + /// + /// Credentials interface. An instance of credentials is created + /// when creating a and it is updated when login + /// or when refreshing tokens. + /// + internal interface ICredentials + { + /// + /// Raised when login or when token is refreshed + /// + event EventHandler OnCredentialsChanged; + + /// + /// Gets the access token. + /// + /// The access token. + string AccessToken { get; } + + /// + /// Gets the type of the token. In our case Bearer + /// + /// The type of the token. + string TokenType { get; } + + /// + /// Gets the refresh token. + /// + /// The refresh token. + string RefreshToken { get; } + + /// + /// Gets a value indicating whether this can be refreshed. + /// + /// true if can be refreshed; otherwise, false. + bool CanRefresh { get; } + + /// + /// Checks if credentials are valid or expired + /// + /// true, if credentials are valid, false otherwise. + bool AreValid(); + + /// + /// Update the credentials with the specified token. + /// + /// Token. + void Update(Token token); + } +} \ No newline at end of file diff --git a/Bynder/Sdk/Settings/Validators/ConfigurationValidator.cs b/Bynder/Sdk/Settings/Validators/ConfigurationValidator.cs new file mode 100644 index 0000000..f3904d3 --- /dev/null +++ b/Bynder/Sdk/Settings/Validators/ConfigurationValidator.cs @@ -0,0 +1,40 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using Bynder.Sdk.Exceptions; +using Bynder.Sdk.Service; +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Settings.Validators +{ + /// + /// Configuration validator. + /// + internal class ConfigurationValidator + { + /// + /// Validate the specified configuration has all the required information for the + /// SDK to work. + /// Throws if configuration is not valid. + /// + /// Configuration. + public void Validate(Configuration configuration) + { + if (configuration.ClientId == null) + { + throw new InvalidConfigurationException("Missing Client Id"); + } + + if (configuration.ClientSecret == null) + { + throw new InvalidConfigurationException("Missing Client Secret"); + } + + if (configuration.Token == null + && configuration.RedirectUri == null) + { + throw new InvalidConfigurationException("Either Token or Redirect Uri need to be passed"); + } + } + } +} diff --git a/Bynder/Sdk/Settings/Validators/OAuth2ServiceConfigurationValidator.cs b/Bynder/Sdk/Settings/Validators/OAuth2ServiceConfigurationValidator.cs new file mode 100644 index 0000000..3faf378 --- /dev/null +++ b/Bynder/Sdk/Settings/Validators/OAuth2ServiceConfigurationValidator.cs @@ -0,0 +1,27 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using Bynder.Sdk.Exceptions; +using Bynder.Sdk.Settings; + +namespace Bynder.Sdk.Settings.Validators +{ + /// + /// OAuth2 service configuration validator. + /// + internal class OAuth2ServiceConfigurationValidator + { + /// + /// Validates the specified configuration is valid to be used by . + /// Throws if configuration is not valid. + /// + /// Configuration. + internal void Validate(Configuration configuration) + { + if (configuration.RedirectUri == null) + { + throw new InvalidConfigurationException("Missing Client Secret"); + } + } + } +} diff --git a/Bynder/Sdk/Utils/Url.cs b/Bynder/Sdk/Utils/Url.cs new file mode 100644 index 0000000..39035e4 --- /dev/null +++ b/Bynder/Sdk/Utils/Url.cs @@ -0,0 +1,30 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Bynder.Sdk.Utils +{ + /// + /// URL Utilities Class. + /// + public static class Url + { + /// + /// Converts dictionary to query parameters. + /// + /// Escaped query. + /// dictionary with parameters. + public static string ConvertToQuery(IDictionary parameters) + { + var encodedValues = parameters.Keys + .OrderBy(key => key) + .Select(key => $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(parameters[key])}"); + var queryUri = string.Join("&", encodedValues); + + return queryUri; + } + } +} \ No newline at end of file diff --git a/Bynder/Test/Api/Converters/BooleanJsonConverterTest.cs b/Bynder/Test/Api/Converters/BooleanJsonConverterTest.cs new file mode 100644 index 0000000..0d1bf38 --- /dev/null +++ b/Bynder/Test/Api/Converters/BooleanJsonConverterTest.cs @@ -0,0 +1,23 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.IO; +using Bynder.Sdk.Api.Converters; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Bynder.Test.Api.Converters +{ + public class BooleanJsonConverterTest + { + [Fact] + public void CanConvertOnlyWhenTypeIsBoolean() + { + BooleanJsonConverter converter = new BooleanJsonConverter(); + Assert.False(converter.CanConvert(typeof(int))); + Assert.False(converter.CanConvert(typeof(string))); + Assert.True(converter.CanConvert(typeof(bool))); + } + } +} \ No newline at end of file diff --git a/Bynder/Test/Api/Converters/DateTimeOffsetConverterTest.cs b/Bynder/Test/Api/Converters/DateTimeOffsetConverterTest.cs new file mode 100644 index 0000000..a266d3b --- /dev/null +++ b/Bynder/Test/Api/Converters/DateTimeOffsetConverterTest.cs @@ -0,0 +1,30 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Api.Converters; +using Xunit; + +namespace Bynder.Test.Api.Converters +{ + public class DateTimeOffsetConverterTest + { + [Fact] + public void CanConvertOnlyWhenTypeIsDateTimeOffset() + { + DateTimeOffsetConverter converter = new DateTimeOffsetConverter(); + Assert.False(converter.CanConvert(typeof(int))); + Assert.False(converter.CanConvert(typeof(string))); + Assert.False(converter.CanConvert(typeof(bool))); + Assert.True(converter.CanConvert(typeof(DateTimeOffset))); + } + + [Fact] + public void ConvertReturnsStringWithDate() + { + DateTimeOffsetConverter converter = new DateTimeOffsetConverter(); + var date = converter.Convert(new DateTimeOffset(new DateTime(1000, 1, 1))); + Assert.Equal("1000-01-01T00:00:00Z", date); + } + } +} \ No newline at end of file diff --git a/Bynder/Test/Api/Converters/JsonConverterTest.cs b/Bynder/Test/Api/Converters/JsonConverterTest.cs new file mode 100644 index 0000000..a3519e6 --- /dev/null +++ b/Bynder/Test/Api/Converters/JsonConverterTest.cs @@ -0,0 +1,27 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Bynder.Sdk.Api.Converters; +using Xunit; + +namespace Bynder.Test.Api.Converters +{ + public class JsonConverterTest + { + [Fact] + public void ConvertReturnsString() + { + JsonConverter converter = new JsonConverter(); + string convertedValue = converter.Convert(new TestClass { + Name = "name" + }); + Assert.Equal("{\"Name\":\"name\"}", convertedValue); + } + } + + public class TestClass { + public string Name { get; set; } + } +} diff --git a/Bynder/Test/Api/Converters/ListConverterTest.cs b/Bynder/Test/Api/Converters/ListConverterTest.cs new file mode 100644 index 0000000..1076931 --- /dev/null +++ b/Bynder/Test/Api/Converters/ListConverterTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Bynder.Sdk.Api.Converters; +using Xunit; + +namespace Bynder.Test.Api.Converters +{ + public class ListConverterTest + { + [Fact] + public void CanConvertOnlyWhenTypeIsEnumerableOfStrings() + { + ListConverter converter = new ListConverter(); + Assert.False(converter.CanConvert(typeof(int))); + Assert.False(converter.CanConvert(typeof(IEnumerable))); + Assert.True(converter.CanConvert(typeof(IEnumerable))); + } + + [Fact] + public void ConvertReturnsJoinedList() + { + ListConverter converter = new ListConverter(); + string convertedList = converter.Convert(new List { "item1" }); + Assert.Equal("item1", convertedList); + + convertedList = converter.Convert(new List { "item1" , "item2" }); + Assert.Equal("item1,item2", convertedList); + } + } +} diff --git a/Bynder/Test/Api/Converters/LowerCaseEnumConverterTest.cs b/Bynder/Test/Api/Converters/LowerCaseEnumConverterTest.cs new file mode 100644 index 0000000..aa8eb08 --- /dev/null +++ b/Bynder/Test/Api/Converters/LowerCaseEnumConverterTest.cs @@ -0,0 +1,33 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Bynder.Sdk.Api.Converters; +using Xunit; + +namespace Bynder.Test.Api.Converters +{ + public class LowerCaseEnumConverterTest + { + [Fact] + public void CanConvertOnlyWhenTypeIsEnum() + { + LowerCaseEnumConverter converter = new LowerCaseEnumConverter(); + Assert.False(converter.CanConvert(typeof(int))); + Assert.False(converter.CanConvert(typeof(bool))); + Assert.False(converter.CanConvert(typeof(string))); + Assert.True(converter.CanConvert(typeof(Example))); + } + + [Fact] + public void ConvertReturnsLowerCaseString() + { + LowerCaseEnumConverter converter = new LowerCaseEnumConverter(); + string convertedValue = converter.Convert(Example.Example); + Assert.Equal("example", convertedValue); + } + } + + public enum Example { Example }; +} diff --git a/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs b/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs new file mode 100644 index 0000000..4a8bf95 --- /dev/null +++ b/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs @@ -0,0 +1,179 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Query.Decoder; +using Bynder.Sdk.Settings; +using Moq; +using Xunit; + +namespace Bynder.Test.Api.RequestSender +{ + public class ApiRequestSenderTest + { + [Fact] + public async Task WhenRequestIsPostThenParametersAreAddedToContent() + { + var httpSenderMock = new Mock(); + var query = new StubQuery + { + Item1 = "Value" + }; + var accessToken = "access_token"; + + using (ApiRequestSender apiRequestSender = new ApiRequestSender( + new Configuration{ + BaseUrl = new Uri("https://example.bynder.com"), + }, + GetCredentials(true, accessToken), + httpSenderMock.Object + )) + { + var apiRequest = new ApiRequest() + { + Path = "/fake/api", + HTTPMethod = HttpMethod.Post, + Query = query + }; + + await apiRequestSender.SendRequestAsync(apiRequest); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains("/fake/api") + && req.Method == HttpMethod.Post + && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" + && req.Content.ReadAsStringAsync().Result.Contains("Item1=Value") + ))); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Once); + } + } + + + [Fact] + public async Task WhenCredentialInvalidTwoRequestsSent() + { + var httpSenderMock = new Mock(); + + var query = new StubQuery + { + Item1 = "Value" + }; + var accessToken = "access_token"; + + using (ApiRequestSender apiRequestSender = new ApiRequestSender( + new Configuration{ + BaseUrl = new Uri("https://example.bynder.com"), + }, + GetCredentials(false, accessToken), + httpSenderMock.Object + )) + { + var apiRequest = new ApiRequest() + { + Path = "/fake/api", + HTTPMethod = HttpMethod.Get, + Query = query + }; + + await apiRequestSender.SendRequestAsync(apiRequest); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains("/oauth2/token") + && req.Method == HttpMethod.Post + ))); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains("/fake/api") + && req.Method == HttpMethod.Get + && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" + && req.RequestUri.Query.Contains("Item1=Value") + ))); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Exactly(2)); + } + } + + [Fact] + public async Task WhenRequestIsGetThenParametersAreAddedToUrl() + { + var httpSenderMock = new Mock(); + var query = new StubQuery + { + Item1 = "Value" + }; + var accessToken = "access_token"; + + using (ApiRequestSender apiRequestSender = new ApiRequestSender( + new Configuration{ + BaseUrl = new Uri("https://example.bynder.com"), + }, + GetCredentials(true, accessToken), + httpSenderMock.Object + )) + { + var apiRequest = new ApiRequest + { + Path = "/fake/api", + HTTPMethod = HttpMethod.Get, + Query = query + }; + + await apiRequestSender.SendRequestAsync(apiRequest); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains("/fake/api") + && req.Method == HttpMethod.Get + && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" + && req.RequestUri.Query.Contains("Item1=Value") + ))); + + httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Once); + } + } + + private ICredentials GetCredentials(bool valid = true, string accessToken = null) + { + var credentialsMock = new Mock(); + credentialsMock + .Setup(mock => mock.AreValid()) + .Returns(valid); + + credentialsMock + .SetupGet(mock => mock.AccessToken) + .Returns(accessToken); + + credentialsMock + .SetupGet(mock => mock.TokenType) + .Returns("Bearer"); + + return credentialsMock.Object; + } + + /// + /// Stub query for testing purposes. + /// + private class StubQuery + { + /// + /// Stub property + /// + [ApiField("Item1")] + public string Item1 { get; set; } + } + } +} diff --git a/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs b/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs new file mode 100644 index 0000000..82bf28d --- /dev/null +++ b/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs @@ -0,0 +1,28 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Net; +using System.Net.Http; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Test.Utils; +using Xunit; + +namespace Bynder.Test.Api.RequestSender +{ + public class HttpRequestSenderTest + { + [Fact] + public void WhenErrorReceivedAnExceptionIsThown() + { + using (var testHttpListener = new TestHttpListener(HttpStatusCode.Forbidden, null)) + { + using (HttpRequestSender apiRequestSender = new HttpRequestSender()) + { + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, testHttpListener.ListeningUrl); + + Assert.ThrowsAsync(async () => await apiRequestSender.SendHttpRequest(requestMessage)); + } + } + } + } +} diff --git a/Bynder/Test/AssetBank/AssetBankTest.cs b/Bynder/Test/AssetBank/AssetBankTest.cs deleted file mode 100644 index 423ada3..0000000 --- a/Bynder/Test/AssetBank/AssetBankTest.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Bynder.Api.Impl; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Models; -using Moq; -using NUnit.Framework; - -namespace Bynder.Test.AssetBank -{ - /// - /// Class to test Asset bank implementation - /// - [TestFixture] - public class AssetBankTest - { - /// - /// Tests that when , request has correct values - /// in Url, and HTTPMethod - /// - /// Task to wait - [Test] - public async Task WhenGetMediaInformationByIdThenRequestHasCorrectMediaValues() - { - const string MediaId = "8888"; - Media returnedMedia = new Media(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())).Returns(Task.FromResult(returnedMedia)); - - var assetBankManager = new AssetBankManager(mock.Object); - var media = await assetBankManager.RequestMediaInfoAsync(new MediaInformationQuery { MediaId = MediaId }); - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/media/{MediaId}/" - && req.HTTPMethod == HttpMethod.Get - && ((MediaInformationQuery)req.Query).Versions == 1))); - - Assert.AreEqual(returnedMedia, media); - } - - /// - /// Tests that when is called, request has correct values in - /// Url, HTTPMethod and Query. - /// - /// Task to wait - [Test] - public async Task WhenGetMediaThenRequestContainsExpectedValues() - { - IList returnedMediaList = new List(); - - var query = new MediaQuery - { - PropertyOptionId = { "12345", "123123" }, - Limit = 50, - Page = 1 - }; - - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>>())).Returns(Task.FromResult(returnedMediaList)); - - var assetBankManager = new AssetBankManager(mock.Object); - var mediaList = await assetBankManager.RequestMediaListAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>>(req => req.Uri == $"/api/v4/media/" - && req.HTTPMethod == HttpMethod.Get - && req.Query == query))); - Assert.AreEqual(returnedMediaList, mediaList); - } - - /// - /// Tests that when "/>, request has correct - /// values in Url and HTTPMethod - /// - /// Task to wait - [Test] - public async Task WhenGetMetapropertiesThenRequestContainsExpectedValues() - { - var metaproperty = new Metaproperty(); - - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>>())) - .Returns(Task.FromResult>(new Dictionary - { - { "metaproperty1", metaproperty } - })); - - var assetBankManager = new AssetBankManager(mock.Object); - var metaproperties = await assetBankManager.GetMetapropertiesAsync(); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>>(req => req.Uri == $"/api/v4/metaproperties/" - && req.HTTPMethod == HttpMethod.Get - && req.Query == null))); - - Assert.AreEqual(1, metaproperties.Count); - Assert.AreEqual(metaproperties["metaproperty1"], metaproperty); - } - - /// - /// Tests that when Download Url is called then request has correct values. - /// It tests when MediaItemId is specified and when it's not. - /// - /// Task to wait - [Test] - public async Task WhenDownloadUrlIsRequestedThenRequestContainsExpectedValues() - { - const string MediaId = "MediaId"; - const string MediaItemId = "MediaItemId"; - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())) - .Returns(Task.FromResult(new DownloadFileUrl())); - - var assetBankManager = new AssetBankManager(mock.Object); - var downloadFileUrl = await assetBankManager.GetDownloadFileUrlAsync(new DownloadMediaQuery - { - MediaId = MediaId - }); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/media/{MediaId}/download/" - && req.HTTPMethod == HttpMethod.Get))); - - downloadFileUrl = await assetBankManager.GetDownloadFileUrlAsync(new DownloadMediaQuery - { - MediaId = MediaId, - MediaItemId = MediaItemId - }); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/media/{MediaId}/download/{MediaItemId}/" - && req.HTTPMethod == HttpMethod.Get))); - } - } -} diff --git a/Bynder/Test/AssetBank/FileUploaderTest.cs b/Bynder/Test/AssetBank/FileUploaderTest.cs deleted file mode 100644 index 4d498da..0000000 --- a/Bynder/Test/AssetBank/FileUploaderTest.cs +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Impl.Upload; -using Bynder.Api.Queries; -using Bynder.Models; -using Moq; -using NUnit.Framework; - -namespace Bynder.Test.AssetBank -{ - /// - /// File to test implementation - /// - [TestFixture] - public class FileUploaderTest - { - /// - /// Import id to return from finalize upload and poll status - /// - private const string ImportId = "ImportId"; - - /// - /// Tests that correct sequence is called when - /// The order it tests is: - /// 1. Init upload - /// 2. Get closest s3 endpoint - /// 3. Upload part to Amazon - /// 4. Register chunk in Bynder - /// 5. Finalize upload. - /// 6. Poll status - /// 7. Save - /// - /// Task to wait - [Test] - public async Task WhenFileUploadedThenUploadSequenceIsCalled() - { - var mock = new Mock(MockBehavior.Strict); - var awsmock = new Mock(MockBehavior.Strict); - var sequence = new MockSequence(); - - var filename = Path.GetTempFileName(); - var uploadRequest = GetUploadRequest(); - var finalizeResponse = GetFinalizeResponse(); - - // Init upload - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetValidUploadRequest(filename))) - .Returns(Task.FromResult(uploadRequest)); - - // Get Closest s3 endpoint to upload parts - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetValidClosestS3EndpointRequest())) - .Returns(Task.FromResult("\"http://test.amazon.com/\"")); - - // Upload Part to Amazon - awsmock.InSequence(sequence).Setup(amazonApiMock => amazonApiMock.UploadPartToAmazon( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())).Returns(Task.FromResult(true)); - - // Register chunk in bynder - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetValidRegisterChunkRequest(uploadRequest))) - .Returns(Task.FromResult(string.Empty)); - - // Finalizes the upload - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetValidFinalizeResponseRequest(uploadRequest))) - .Returns(Task.FromResult(finalizeResponse)); - - // Poll status - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetValidPollStatus(finalizeResponse))) - .Returns(Task.FromResult(GetPollStatus())); - - // Saves media - mock.InSequence(sequence).Setup(reqSenderMock => reqSenderMock.SendRequestAsync(GetSaveMediaRequest(filename, finalizeResponse))) - .Returns(Task.FromResult(string.Empty)); - - FileUploader uploader = new FileUploader(mock.Object, awsmock.Object); - using (var fileStream = File.OpenWrite(filename)) - { - var bytes = Encoding.UTF8.GetBytes("mockdata"); - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - - try - { - await uploader.UploadFile(new UploadQuery - { - Filepath = filename - }); - } - finally - { - File.Delete(filename); - } - } - - /// - /// Returns Request that has valid values for upload request - /// - /// filename of the file we want to upload - /// request to initialize upload - private Request GetValidUploadRequest(string filename) - { - return It.Is>(req => - req.Uri == "/api/upload/init" - && req.HTTPMethod == HttpMethod.Post - && ((RequestUploadQuery)req.Query).Filename == filename); - } - - /// - /// Returns Request that has valid values to get Closest S3 enpdoint - /// - /// request to get closest endpoint - private Request GetValidClosestS3EndpointRequest() - { - return It.Is>(req => - req.Uri == "/api/upload/endpoint" - && req.HTTPMethod == HttpMethod.Get - && req.Query == null); - } - - /// - /// Returns Request that has valid values to register chunk - /// - /// upload request. Needed to validate request values - /// request to register chunk - private Request GetValidRegisterChunkRequest(UploadRequest uploadRequest) - { - return It.Is>(req => - IsRegisterChunkValid(req, uploadRequest)); - } - - /// - /// Returns Request that has valid values to finalize response - /// - /// upload request. Needed to validate request values - /// request to finalize upload - private Request GetValidFinalizeResponseRequest(UploadRequest uploadRequest) - { - return It.Is>(req => IsFinalizeResponseValid(req, uploadRequest)); - } - - /// - /// Returns Request that has valid values to poll status - /// - /// Finalize response. Needed to validate request values - /// Request to poll status - private Request GetValidPollStatus(FinalizeResponse finalizeResponse) - { - return It.Is>(req => - req.Uri == "/api/v4/upload/poll/" - && req.HTTPMethod == HttpMethod.Get - && ((PollQuery)req.Query).Items.Contains(finalizeResponse.ImportId)); - } - - /// - /// Returns Request that has valid values to save media - /// - /// filename of the media. Needed to validate request values - /// finalize response. Needed to validate request values - /// Request to save media - private Request GetSaveMediaRequest(string filename, FinalizeResponse finalizeResponse) - { - return It.Is>(req => - req.Uri == $"/api/v4/media/save/{finalizeResponse.ImportId}/" - && req.HTTPMethod == HttpMethod.Post - && ((SaveMediaQuery)req.Query).Filename == Path.GetFileName(filename)); - } - - /// - /// Helper function to check if a request is valid to call register chunk. - /// - /// request to validate - /// upload request - /// true if request is valid - private bool IsRegisterChunkValid(Request request, UploadRequest uploadRequest) - { - var registerChunkQuery = (RegisterChunkQuery)request.Query; - - return request.Uri == $"/api/v4/upload/{uploadRequest.S3File.UploadId}/" - && request.HTTPMethod == HttpMethod.Post - && registerChunkQuery.ChunkNumber == "1" - && registerChunkQuery.TargetId == uploadRequest.S3File.TargetId - && registerChunkQuery.S3Filename == $"{uploadRequest.S3Filename}/p1"; - } - - /// - /// Helper function to check if a request is valid to call finalize upload. - /// - /// request to validate - /// upload request - /// true if request is valid - private bool IsFinalizeResponseValid(Request request, UploadRequest uploadRequest) - { - var registerChunkQuery = (FinalizeUploadQuery)request.Query; - - return request.Uri == $"/api/v4/upload/{uploadRequest.S3File.UploadId}/" - && request.HTTPMethod == HttpMethod.Post - && registerChunkQuery.Chunks == "1" - && registerChunkQuery.TargetId == uploadRequest.S3File.TargetId - && registerChunkQuery.S3Filename == $"{uploadRequest.S3Filename}/p1"; - } - - /// - /// Returns Stub date to return when Request Upload is called - /// - /// Returns stub instance - private UploadRequest GetUploadRequest() - { - return new UploadRequest - { - S3File = new S3File - { - TargetId = "targetid", - UploadId = "uploadid" - }, - S3Filename = "filename" - }; - } - - /// - /// Returns Stub finalize response to return when Finalize upload is called - /// - /// Returns stub instance - private FinalizeResponse GetFinalizeResponse() - { - return new FinalizeResponse - { - ImportId = ImportId - }; - } - - /// - /// Returns Stub finalize response to return when Poll status is called - /// - /// Returns stub instance - private PollStatus GetPollStatus() - { - return new PollStatus - { - ItemsDone = new HashSet { ImportId } - }; - } - } -} diff --git a/Bynder/Test/Bynder.Test.csproj b/Bynder/Test/Bynder.Test.csproj index f4a2535..9918da2 100644 --- a/Bynder/Test/Bynder.Test.csproj +++ b/Bynder/Test/Bynder.Test.csproj @@ -2,8 +2,8 @@ netcoreapp2.0 true - 1.3.0 - 1.3.0 + 2.0.0 + 2.0.0 Bynder Bynder.Test Copyright © Bynder @@ -14,18 +14,13 @@ - - + + - + - - - PreserveNewest - - diff --git a/Bynder/Test/BynderApiTest.cs b/Bynder/Test/BynderApiTest.cs deleted file mode 100644 index 1f16d3e..0000000 --- a/Bynder/Test/BynderApiTest.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Net.Http; -using System.Threading.Tasks; -using System.Web; -using Bynder.Api; -using Bynder.Api.Impl; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Models; -using Bynder.Test.Utils; -using Moq; -using NUnit.Framework; - -namespace Bynder.Test -{ - /// - /// Tests the login methods - /// - public class BynderApiTest - { - /// - /// Tests that when login is done using , request has appropiate values in - /// query, HTTPMethod and Url - /// - /// Task to wait - [Test] - public async Task WhenLoginCalledWithLoginQueryThenRequestHasCorrectValues() - { - const string Username = "bynder_user"; - const string Password = "bynder_password"; - var oauthRequestSenderMock = new Mock(); - var user = new User { Access = true }; - oauthRequestSenderMock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())) - .Returns(Task.FromResult(user)); - - var credentials = TestConfiguration.GetCredentials(); - - var bynderApi = new BynderApi(credentials, null, oauthRequestSenderMock.Object); - - Assert.AreEqual(user, await bynderApi.LoginAsync(Username, Password)); - - oauthRequestSenderMock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>( - req => ValidateLoginRequest(req, credentials.CONSUMER_KEY, Username, Password)))); - } - - /// - /// Tests that Url returned by authorize Url is correct - /// - [Test] - public void WhenGetAuthorizeUrlCalledThenUrlReturnedHasAppropiateUrlAndToken() - { - Settings settings = TestConfiguration.GetSettings(); - IBynderApi api = BynderApiFactory.Create(settings); - var noCallback = api.GetAuthorizeUrl(null); - Assert.AreEqual(string.Format("{0}api/v4/oauth/authorise/?oauth_token={1}", settings.URL, settings.TOKEN), noCallback); - } - - /// - /// Tests that Url returned by authorize Url when callback Url is specified is correct - /// - [Test] - public void WhenGetAuthorizeUrlCalledWithCallbackUrlThenUrlReturnedUrlAddsCallbackParameter() - { - Settings settings = TestConfiguration.GetSettings(); - IBynderApi api = BynderApiFactory.Create(settings); - string callbackUrl = "http://localhost/"; - string encodedUrl = HttpUtility.UrlEncode(callbackUrl); - var noCallback = api.GetAuthorizeUrl(callbackUrl); - Assert.AreEqual(string.Format("{0}api/v4/oauth/authorise/?oauth_token={1}&callback={2}", settings.URL, settings.TOKEN, encodedUrl), noCallback); - } - - /// - /// Tests that when Request tokens is called, then credentials are updated and request has correct values - /// - /// Task to wait - [Test] - public async Task WhenRequestTokensCalledThenRequestHasCorrectValues() - { - const string OauthToken = "FAKE_TOKEN"; - const string OauthTokenSecret = "FAKE_TOKEN_SECRET"; - - var oauthRequestSenderMock = GetIOauthRequestSenderToRespondToTokenCalls(OauthToken, OauthTokenSecret); - var credentials = TestConfiguration.GetCredentials(); - - var bynderApi = new BynderApi(credentials, null, oauthRequestSenderMock.Object); - await bynderApi.GetRequestTokenAsync(); - - oauthRequestSenderMock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>( - req => ValidateRequestForTokenCall(req, "/api/v4/oauth/request_token")))); - - Assert.AreEqual(credentials.ACCESS_TOKEN, OauthToken); - Assert.AreEqual(credentials.ACCESS_TOKEN_SECRET, OauthTokenSecret); - } - - /// - /// Checks that when access token call is called, then credentials are updated and request has correct values - /// - /// Task to wait - [Test] - public async Task WhenAccessTokensCalledThenRequestHasCorrectValues() - { - const string OauthToken = "FAKE_TOKEN"; - const string OauthTokenSecret = "FAKE_TOKEN_SECRET"; - - var credentials = TestConfiguration.GetCredentials(); - var oauthRequestSenderMock = GetIOauthRequestSenderToRespondToTokenCalls(OauthToken, OauthTokenSecret); - var bynderApi = new BynderApi(credentials, null, oauthRequestSenderMock.Object); - await bynderApi.GetAccessTokenAsync(); - - oauthRequestSenderMock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>( - req => ValidateRequestForTokenCall(req, "/api/v4/oauth/access_token")))); - - Assert.AreEqual(credentials.ACCESS_TOKEN, OauthToken); - Assert.AreEqual(credentials.ACCESS_TOKEN_SECRET, OauthTokenSecret); - } - - /// - /// Returns Mock and sets it up to respond to Request for token calls - /// - /// oauth token to include in the response - /// oauth token secret to include in the response - /// Mock instance of - private Mock GetIOauthRequestSenderToRespondToTokenCalls(string oauthToken, string oauthTokenSecret) - { - var oauthRequestSenderMock = new Mock(); - oauthRequestSenderMock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())) - .Returns(Task.FromResult($"oauth_token={oauthToken}&oauth_token_secret={oauthTokenSecret}")); - - return oauthRequestSenderMock; - } - - /// - /// Validates that a request is valid for a token call. - /// It is validated against Uri specified as parameter and checks that we do not deserialize response - /// and that is a POST - /// - /// Request to validate - /// Uri the request should point to (either /api/v4/oauth/access_token or /api/v4/oauth/request_token - /// true if request is valid - private bool ValidateRequestForTokenCall(Request request, string uri) - { - return request.Uri == uri - && request.HTTPMethod == HttpMethod.Post - && request.DeserializeResponse == false; - } - - /// - /// Helper function to validate a login request - /// - /// request to validate - /// consumer id - /// login username - /// login password - /// if request is valid - private bool ValidateLoginRequest(Request request, string consumerId, string username, string password) - { - var internalLoginQuery = (LoginQuery)request.Query; - - return request.Uri == $"/api/v4/users/login/" - && request.HTTPMethod == HttpMethod.Post - && internalLoginQuery.ConsumerId == consumerId - && internalLoginQuery.Password == password - && internalLoginQuery.Username == username; - } - } -} diff --git a/Bynder/Test/Collections/CollectionsManagerTest.cs b/Bynder/Test/Collections/CollectionsManagerTest.cs deleted file mode 100644 index 7c74847..0000000 --- a/Bynder/Test/Collections/CollectionsManagerTest.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Bynder.Api.Impl; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Api.Queries.Collections; -using Bynder.Api.Queries.Collections.PermissionOptions; -using Bynder.Models; -using Moq; -using NUnit.Framework; - -namespace Bynder.Test.Collections -{ - /// - /// Class to test Asset bank implementation - /// - [TestFixture] - public class CollectionsManagerTest - { - /// - /// Tests that when , request has correct values - /// in Url, HTTPMethod and Query. - /// - /// Task to wait - [Test] - public async Task WhenGetCollectionsAsyncCalledContainsExpectedResult() - { - var query = new GetCollectionsQuery() { Limit = 50, Page = 1 }; - - IList mediaCollections = new List(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>>())).Returns(Task.FromResult(mediaCollections)); - - var manager = new CollectionsManager(mock.Object); - var collectionList = await manager.GetCollectionsAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>>(req => req.Uri == $"/api/v4/collections/" - && req.HTTPMethod == HttpMethod.Get - && req.Query == query))); - Assert.AreEqual(mediaCollections, collectionList); - } - - /// - /// Tests that when "/>, request has correct - /// values in Url and HTTPMethod. - /// - /// Id of a collection - /// Task to wait - [TestCase("10D82138-AA72-4824-967C6E425810FBDF")] - public async Task WhenGetCollectionsAsyncCalledContainsExpectedResult(string id) - { - Collection mediaCollection = new Collection(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())).Returns(Task.FromResult(mediaCollection)); - - var manager = new CollectionsManager(mock.Object); - var collection = await manager.GetCollectionAsync(id); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/collections/{id}/" - && req.HTTPMethod == HttpMethod.Get))); - Assert.AreEqual(mediaCollection, collection); - } - - /// - /// Tests that when "/>, request has correct - /// values in Url and HTTPMethod. - /// - /// Id of a collection - /// Task to wait - [TestCase("00000000-0000-0000-0000000000000000")] - public async Task WhenGetMediaAsyncCalledContainsExpectedResult(string id) - { - var query = new GetMediaQuery(id); - - IList mediaList = new List(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>>())).Returns(Task.FromResult(mediaList)); - - var manager = new CollectionsManager(mock.Object); - var mediaOfCollection = await manager.GetMediaAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>>(req => req.Uri == $"/api/v4/collections/{id}/media/" - && req.HTTPMethod == HttpMethod.Get))); - Assert.AreEqual(mediaList, mediaOfCollection); - } - - /// - /// Tests that when "/>, request has correct - /// values in Url and HTTPMethod. - /// - /// Id of a collection - /// Id of a media asset - /// Task to wait - [TestCase("00000000-0000-0000-0000000000000000", "00000000-0000-0000-0000000000000000")] - public async Task WhenAddMediaAsyncCalledContainsExpectedResult(string id, string mediaId) - { - var query = new AddMediaQuery(id, new[] { mediaId }); - - IList mediaList = new List(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())).Returns(Task.FromResult(string.Empty)); - - var manager = new CollectionsManager(mock.Object); - await manager.AddMediaAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/collections/{id}/media/" - && req.HTTPMethod == HttpMethod.Post - && ((AddMediaQuery)req.Query).MediaIds.Contains(mediaId)))); - } - - /// - /// Tests that when "/>, request has correct - /// values in Url and HTTPMethod. - /// - /// Id of a collection - /// Id of a media asset - /// Task to wait - [TestCase("00000000-0000-0000-0000000000000000", "00000000-0000-0000-0000000000000000")] - public async Task WhenRemoveMediaAsyncCalledContainsExpectedResult(string id, string mediaId) - { - var query = new RemoveMediaQuery(id, new[] { mediaId }); - - IList mediaList = new List(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())).Returns(Task.FromResult(string.Empty)); - - var manager = new CollectionsManager(mock.Object); - await manager.RemoveMediaAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/collections/{id}/media/" - && req.HTTPMethod == HttpMethod.Delete - && ((RemoveMediaQuery)req.Query).MediaIds.Contains(mediaId)))); - } - - /// - /// Tests that when , request has correct - /// values in Url and HTTPMethod. - /// - /// Id of a collection - /// Comma separated email addresses of the recipients - /// Task to wait - [TestCase("00000000-0000-0000-0000000000000000", "someone@bynder.com")] - [TestCase("00000000-0000-0000-0000000000000000", "someone1@bynder.com,someone2@bynder.com")] - public async Task WhenShareAsyncCalledContainsExpectedResult(string id, string emailAddresses) - { - IList recipients = emailAddresses.Split(','); - - var query = new ShareQuery(id, recipients, SharingPermission.View); - - IList mediaList = new List(); - var mock = new Mock(); - mock.Setup(reqSenderMock => reqSenderMock.SendRequestAsync(It.IsAny>())).Returns(Task.FromResult(string.Empty)); - - var manager = new CollectionsManager(mock.Object); - await manager.ShareCollectionAsync(query); - - mock.Verify(reqSenderMock - => reqSenderMock.SendRequestAsync(It.Is>(req => req.Uri == $"/api/v4/collections/{id}/share/" - && req.HTTPMethod == HttpMethod.Post - && ((ShareQuery)req.Query).Recipients.Count == recipients.Count - && ((ShareQuery)req.Query).Permission == SharingPermission.View))); - } - } -} diff --git a/Bynder/Test/Converters/DateTimeOffsetConverterTest.cs b/Bynder/Test/Converters/DateTimeOffsetConverterTest.cs deleted file mode 100644 index 1d4d782..0000000 --- a/Bynder/Test/Converters/DateTimeOffsetConverterTest.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using Bynder.Api.Converters; -using NUnit.Framework; - -namespace Bynder.Test.Converters -{ - /// - /// Tests for - /// - [TestFixture] - public class DateTimeOffsetConverterTest - { - /// - /// Checks when a correct type is provided - /// - /// Type to be converted - [TestCase(typeof(DateTimeOffset))] - [TestCase(typeof(DateTimeOffset?))] - public void WhenCanConvertCorrectTypeThenTrue(Type type) - { - Assert.IsTrue(new DateTimeOffsetConverter().CanConvert(type)); - } - - /// - /// Checks when an erroneous type is provided - /// - /// Type to be converted - [TestCase(typeof(TimeSpan))] - [TestCase(typeof(DateTime))] - [TestCase(typeof(object))] - [TestCase(typeof(string))] - public void WhenCanConvertWrongTypeThenFalse(Type type) - { - Assert.IsFalse(new DateTimeOffsetConverter().CanConvert(type)); - } - - /// - /// Checks when an correct value is provided - /// - /// Date to be converted - /// Expected string value - [TestCase("01/20/2018 22:12PM", "2018-01-20T22:12:00Z")] - [TestCase("01/12/2017", "2017-01-12T00:00:00Z")] - public void WhenConvertCorrectDateTimeOffsetThenCorrectExpectedDateString(DateTime date, string expectedDateString) - { - var dateOffset = new DateTimeOffset(date); - Assert.AreEqual(new DateTimeOffsetConverter().Convert(dateOffset), expectedDateString); - } - - /// - /// Checks when an erroneous type value is provided - /// - /// Type to be converted - /// Value to be converted - [TestCase(typeof(string), "")] - [TestCase(typeof(int), 10)] - [TestCase(typeof(DateTimeOffset?), null)] - public void WhenConvertNotDateTimeOffsetOrNullThenEmptyString(Type type, object variable) - { - object objectCreated = variable; - if (objectCreated != null) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - type = Nullable.GetUnderlyingType(type); - } - - if (type.GetConstructor(Type.EmptyTypes) != null && !type.IsAbstract) - { - // this type is constructable with default constructor - objectCreated = Activator.CreateInstance(type); - } - else - { - // no default constructor - objectCreated = Convert.ChangeType(objectCreated, type); - } - } - - Assert.AreEqual(new DateTimeOffsetConverter().Convert(objectCreated), string.Empty); - } - } -} diff --git a/Bynder/Test/Converters/ListConverterTest.cs b/Bynder/Test/Converters/ListConverterTest.cs deleted file mode 100644 index 9554cb0..0000000 --- a/Bynder/Test/Converters/ListConverterTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using Bynder.Api.Converters; -using NUnit.Framework; - -namespace Bynder.Test.Converters -{ - /// - /// Tests for - /// - [TestFixture] - public class ListConverterTest - { - /// - /// Tests that converts a list of strings - /// in a comma separated string - /// - [Test] - public void WhenListOfStringConvertedThenReturnsCommaSeparatedValues() - { - ListConverter converter = new ListConverter(); - var convertedValue = converter.Convert(new List - { - "item1", - "item2", - "item3" - }); - - Assert.AreEqual("item1,item2,item3", convertedValue); - } - - /// - /// Tests that ony returns true for - /// IEnumerable types - /// - [Test] - public void WhenCanConvertCalledWithTypeThenOnlyReturnsToForIEnumerabletypes() - { - var converter = new ListConverter(); - Assert.IsTrue(converter.CanConvert(typeof(List))); - Assert.IsTrue(converter.CanConvert(typeof(IEnumerable))); - Assert.IsFalse(converter.CanConvert(typeof(Dictionary))); - } - } -} diff --git a/Bynder/Test/Converters/LowerCaseEnumConverterTest.cs b/Bynder/Test/Converters/LowerCaseEnumConverterTest.cs deleted file mode 100644 index 38ab200..0000000 --- a/Bynder/Test/Converters/LowerCaseEnumConverterTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System; -using Bynder.Api.Converters; -using NUnit.Framework; - -namespace Bynder.Test.Converters -{ - /// - /// Tests for - /// - [TestFixture] - public class LowerCaseEnumConverterTest - { - /// - /// Enum used for Testing - /// - public enum TestEnum - { - /// - /// First option - /// - First, - - /// - /// Second option - /// - Second - } - - /// - /// Checks when a correct type is provided - /// - /// Type to be converted - [TestCase(typeof(TestEnum))] - public void WhenCanConvertCorrectTypeThenTrue(Type type) - { - Assert.IsTrue(new LowerCaseEnumConverter().CanConvert(type)); - } - - /// - /// Checks when an erroneous type is provided - /// - /// Type to be converted - [TestCase(typeof(TimeSpan))] - [TestCase(typeof(DateTime))] - [TestCase(typeof(object))] - [TestCase(typeof(string))] - public void WhenCanConvertWrongTypeThenFalse(Type type) - { - Assert.IsFalse(new LowerCaseEnumConverter().CanConvert(type)); - } - - /// - /// Checks when an correct value is provided - /// - /// Value of the to be converted - [TestCase("First")] - [TestCase("Second")] - public void WhenConvertCorrectEnumThenCorrectExpectedDateString(string testEnumOption) - { - var option = (TestEnum)Enum.Parse(typeof(TestEnum), testEnumOption, true); - Assert.AreEqual(new LowerCaseEnumConverter().Convert(option), testEnumOption.ToLower()); - } - } -} diff --git a/Bynder/Test/HTTPResponses/User.txt b/Bynder/Test/HTTPResponses/User.txt deleted file mode 100644 index 435ef0e..0000000 --- a/Bynder/Test/HTTPResponses/User.txt +++ /dev/null @@ -1,2 +0,0 @@ -{"tokenSecret":"TOKENSCRETT","userId":"USERID","access":true,"tokenKey":"TOKENKEY"} - diff --git a/Bynder/Test/Model/TokenTest.cs b/Bynder/Test/Model/TokenTest.cs new file mode 100644 index 0000000..6ec5949 --- /dev/null +++ b/Bynder/Test/Model/TokenTest.cs @@ -0,0 +1,26 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Model; +using Xunit; + +namespace Bynder.Test.Model +{ + public class TokenTest + { + [Fact] + public void SetAccessTokenExpirationAddsExpiresInToCurrentDate() { + var token = new Token() { + AccessToken = string.Empty, + ExpiresIn = 3600 + }; + token.SetAccessTokenExpiration(); + var currentDate = DateTimeOffset.Now; + + var expirationDate = token.GetAccessTokenExpiration(); + expirationDate = expirationDate.AddSeconds(-3600); + Assert.Equal(currentDate.Date, expirationDate.Date); + } + } +} \ No newline at end of file diff --git a/Bynder/Test/Oauth/OauthRequestSenderTest.cs b/Bynder/Test/Oauth/OauthRequestSenderTest.cs deleted file mode 100644 index 5e0b4b1..0000000 --- a/Bynder/Test/Oauth/OauthRequestSenderTest.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Bynder.Api.Impl.Oauth; -using Bynder.Api.Queries; -using Bynder.Models; -using Bynder.Test.Utils; -using NUnit.Framework; - -namespace Bynder.Test.Oauth -{ - /// - /// Class to test - /// - [TestFixture] - public class OauthRequestSenderTest - { - /// - /// Tests that an exception is thrown when server returns Forbidden - /// - [Test] - public void WhenSentRequestAndUnauthorizedThenExceptionIsThrown() - { - var settings = TestConfiguration.GetSettings(); - var credentials = new Credentials( - settings.CONSUMER_KEY, - settings.CONSUMER_SECRET, - settings.TOKEN, - settings.TOKEN_SECRET); - - using (var testHttpListener = new TestHttpListener(settings.URL, HttpStatusCode.Forbidden, null)) - { - OauthRequestSender api = new OauthRequestSender(credentials, settings.URL); - var apiRequest = new Request - { - Uri = "/fake/api", - HTTPMethod = HttpMethod.Get - }; - Assert.ThrowsAsync(async () => await api.SendRequestAsync(apiRequest)); - } - } - - /// - /// Tests that response is deserialized to specified Object. - /// - /// Task to wait - [Test] - public async Task WhenRequestSentThenResponseIsDeserializedToObject() - { - var settings = TestConfiguration.GetSettings(); - var credentials = TestConfiguration.GetCredentials(); - - using (var testHttpListener = new TestHttpListener(settings.URL, HttpStatusCode.OK, "HTTPResponses/User.txt")) - { - OauthRequestSender oauthSender = new OauthRequestSender(credentials, settings.URL); - var apiRequest = new Request - { - Uri = "/fake/api", - HTTPMethod = HttpMethod.Get - }; - var user = await oauthSender.SendRequestAsync(apiRequest); - Assert.IsNotNull(user); - Assert.IsNotNull(user.TokenKey); - Assert.IsNotNull(user.TokenSecret); - Assert.IsNotNull(user.UserId); - } - } - - /// - /// Tests that oauth header is added for a request. - /// - /// Task to wait - [Test] - public async Task WhenSendRequestThenAuthHeaderIsAdded() - { - var settings = TestConfiguration.GetSettings(); - var credentials = TestConfiguration.GetCredentials(); - - using (var testHttpListener = new TestHttpListener(settings.URL, HttpStatusCode.OK, "HTTPResponses/User.txt")) - { - bool containsAuthHeader = false; - testHttpListener.MessageReceived += (sender, requestEventArgs) => - { - containsAuthHeader = requestEventArgs.Request.Headers["Authorization"] != null; - }; - - OauthRequestSender oauthSender = new OauthRequestSender(credentials, settings.URL); - var apiRequest = new Request - { - Uri = "/fake/api", - HTTPMethod = HttpMethod.Get - }; - var user = await oauthSender.SendRequestAsync(apiRequest); - Assert.IsTrue(containsAuthHeader); - } - } - - /// - /// Tests that when Request is POST then parameters are added to the body - /// - /// Task to wait - [Test] - public async Task WhenRequestIsPostThenParametersAreAddedToBody() - { - var settings = TestConfiguration.GetSettings(); - var credentials = TestConfiguration.GetCredentials(); - - var query = new StubQuery - { - Item1 = "Value" - }; - - using (var testHttpListener = new TestHttpListener(settings.URL, HttpStatusCode.OK, null)) - { - bool containsItemInBody = false; - testHttpListener.MessageReceived += (sender, requestEventArgs) => - { - var request = requestEventArgs.Request; - using (var streamReader = new StreamReader(request.InputStream)) - { - var str = streamReader.ReadToEnd(); - containsItemInBody = str.Contains("Item1") && str.Contains(query.Item1); - } - }; - - OauthRequestSender oauthSender = new OauthRequestSender(credentials, settings.URL); - var apiRequest = new Request - { - Uri = "/fake/api", - HTTPMethod = HttpMethod.Post, - Query = query - }; - var user = await oauthSender.SendRequestAsync(apiRequest); - Assert.IsTrue(containsItemInBody); - } - } - - /// - /// Tests that when request is GET, parameters are added to the Url. - /// - /// Task to wait - [Test] - public async Task WhenRequestIsGetThenParametersAreAddedToUrl() - { - var settings = TestConfiguration.GetSettings(); - var credentials = TestConfiguration.GetCredentials(); - - var query = new StubQuery - { - Item1 = "Value" - }; - - using (var testHttpListener = new TestHttpListener(settings.URL, HttpStatusCode.OK, null)) - { - bool containsItemInUrl = false; - testHttpListener.MessageReceived += (sender, requestEventArgs) => - { - var request = requestEventArgs.Request; - containsItemInUrl = request.Url.Query.Contains("Item1") - && request.Url.Query.Contains("Value"); - }; - - OauthRequestSender oauthSender = new OauthRequestSender(credentials, settings.URL); - var apiRequest = new Request - { - Uri = "/fake/api", - HTTPMethod = HttpMethod.Get, - Query = query - }; - var user = await oauthSender.SendRequestAsync(apiRequest); - Assert.IsTrue(containsItemInUrl); - } - } - - /// - /// Stub query for testing purposes. - /// - private class StubQuery - { - /// - /// Stub property - /// - [APIField("Item1")] - public string Item1 { get; set; } - } - } -} diff --git a/Bynder/Test/Converters/QueryDecoderTest.cs b/Bynder/Test/Query/Decoder/QueryDecoderTest.cs similarity index 57% rename from Bynder/Test/Converters/QueryDecoderTest.cs rename to Bynder/Test/Query/Decoder/QueryDecoderTest.cs index 5d50c3a..03b7ce7 100644 --- a/Bynder/Test/Converters/QueryDecoderTest.cs +++ b/Bynder/Test/Query/Decoder/QueryDecoderTest.cs @@ -2,23 +2,18 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System; -using Bynder.Api.Converters; -using Bynder.Api.Queries; -using NUnit.Framework; +using Bynder.Sdk.Query.Decoder; +using Xunit; -namespace Bynder.Test.Converters +namespace Bynder.Test.Api { - /// - /// Class to test - /// - [TestFixture] public class QueryDecoderTest { /// - /// Tests that returns - /// only the parameters properties that have the attribute. + /// Tests that returns + /// only the parameters properties that have the attribute. /// - [Test] + [Fact] public void WhenQueryPassedThenOnlyAPIFieldAttributesAreReturned() { var queryDecoder = new QueryDecoder(); @@ -30,17 +25,17 @@ public void WhenQueryPassedThenOnlyAPIFieldAttributesAreReturned() }); // Property Item3 should not appear as it does not have APIField attribute - Assert.AreEqual(2, parameters.Count); + Assert.Equal(2, parameters.Count); - Assert.AreEqual("1", parameters["Item1"]); - Assert.AreEqual("2", parameters["Item2"]); + Assert.Equal("1", parameters["Item1"]); + Assert.Equal("2", parameters["Item2"]); } /// - /// Tests that calls - /// Converter for properties that specify converter + /// Tests that calls + /// converter for properties that specify converter. /// - [Test] + [Fact] public void WhenQueryAttributeHasConverterThenParameterValueIsConverted() { var queryDecoder = new QueryDecoder(); @@ -49,52 +44,52 @@ public void WhenQueryAttributeHasConverterThenParameterValueIsConverted() Item1 = "1" }); - Assert.AreEqual("Converted", parameters["Item1"]); + Assert.Equal("Converted", parameters["Item1"]); } /// - /// Stub class only for testing purposes + /// Stub class only for testing purposes. /// private class StubQuery { /// - /// Stub property + /// Stub property. /// - [APIField("Item1")] + [ApiField("Item1")] public string Item1 { get; set; } /// - /// Stub property + /// Stub property. /// - [APIField("Item2")] + [ApiField("Item2")] public string Item2 { get; set; } /// - /// Stub property + /// Stub property. /// public string Item3 { get; set; } } /// - /// Stub converter only used for testing purposes + /// Stub converter only used for testing purposes. /// - private class StubConverter : ITypeToStringConverter + private class StubDecoder : IParameterDecoder { /// - /// Check + /// Check . /// - /// Check - /// Check + /// Check + /// Check public bool CanConvert(Type typeToConvert) { return true; } /// - /// Check + /// Check . /// - /// Check - /// Check + /// Check + /// Check public string Convert(object value) { return "Converted"; @@ -102,14 +97,14 @@ public string Convert(object value) } /// - /// Stub converter query only used for testing purposes + /// Stub converter query only used for testing purposes. /// private class StubConverterQuery { /// - /// Stub property + /// Stub property. /// - [APIField("Item1", Converter = typeof(StubConverter))] + [ApiField("Item1", Converter = typeof(StubDecoder))] public string Item1 { get; set; } } } diff --git a/Bynder/Test/Service/Asset/AssetServiceTest.cs b/Bynder/Test/Service/Asset/AssetServiceTest.cs new file mode 100644 index 0000000..9a8af11 --- /dev/null +++ b/Bynder/Test/Service/Asset/AssetServiceTest.cs @@ -0,0 +1,140 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Service.Asset; +using Moq; +using Xunit; + +namespace Bynder.Test.Service.Asset +{ + public class AssetServiceTest + { + [Fact] + public async Task GetBrandsCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = (IList) new List(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var brandList = await assetService.GetBrandsAsync(); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == "/api/v4/brands/" + && req.HTTPMethod == HttpMethod.Get + && req.Query == null))); + + Assert.Equal(result, brandList); + } + + [Fact] + public async Task GetMetapropertiesCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = (IDictionary) new Dictionary(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var metaproperties = await assetService.GetMetapropertiesAsync(); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == "/api/v4/metaproperties/" + && req.HTTPMethod == HttpMethod.Get + && req.Query == null))); + + Assert.Equal(result, metaproperties); + } + + [Fact] + public async Task GetMediaListCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = (IList) new List(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var mediaQuery = new MediaQuery(); + var mediaList = await assetService.GetMediaListAsync(mediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == "/api/v4/media/" + && req.HTTPMethod == HttpMethod.Get + && req.Query == mediaQuery))); + + Assert.Equal(result, mediaList); + } + + [Fact] + public async Task GetDownloadFileUrlCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = new DownloadFileUrl(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var downloadMediaQuery = new DownloadMediaQuery{ + MediaId = "mediaId" + }; + var downloadUrl = await assetService.GetDownloadFileUrlAsync(downloadMediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/media/{downloadMediaQuery.MediaId}/download/" + && req.HTTPMethod == HttpMethod.Get))); + + Assert.Equal(result.S3File, downloadUrl); + } + + [Fact] + public async Task GetMediaInfoCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = new Media(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var mediaInformationQuery = new MediaInformationQuery{ + MediaId = "mediaId" + }; + var media = await assetService.GetMediaInfoAsync(mediaInformationQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/media/{mediaInformationQuery.MediaId}/" + && req.HTTPMethod == HttpMethod.Get + && req.Query == mediaInformationQuery))); + + Assert.Equal(result, media); + } + + [Fact] + public async Task ModifyMediaCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var assetService = new AssetService(apiRequestSender.Object); + var modifyMediaQuery = new ModifyMediaQuery("mediaId"); + await assetService.ModifyMediaAsync(modifyMediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/media/{modifyMediaQuery.MediaId}/" + && req.HTTPMethod == HttpMethod.Post + && req.Query == modifyMediaQuery + && req.DeserializeResponse == false))); + } + } +} diff --git a/Bynder/Test/Service/Collection/CollectionServiceTest.cs b/Bynder/Test/Service/Collection/CollectionServiceTest.cs new file mode 100644 index 0000000..9478234 --- /dev/null +++ b/Bynder/Test/Service/Collection/CollectionServiceTest.cs @@ -0,0 +1,170 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Query.Collection; +using Bynder.Sdk.Service.Collection; +using Moq; +using Xunit; + +namespace Bynder.Test.Service.Collection +{ + public class CollectionServiceTest + { + [Fact] + public async Task CreateCollectionCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var createCollectionQuery = new CreateCollectionQuery("name"); + await collectionService.CreateCollectionAsync(createCollectionQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == "/api/v4/collections/" + && req.HTTPMethod == HttpMethod.Post + && req.Query == createCollectionQuery + && req.DeserializeResponse == false))); + } + + [Fact] + public async Task DeleteCollectionCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var collectionId = "collectionId"; + await collectionService.DeleteCollectionAsync(collectionId); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/collections/{collectionId}/" + && req.HTTPMethod == HttpMethod.Delete))); + } + + [Fact] + public async Task GetCollectionCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = new Bynder.Sdk.Model.Collection(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var collectionId = "collectionId"; + var collection = await collectionService.GetCollectionAsync(collectionId); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/collections/{collectionId}/" + && req.HTTPMethod == HttpMethod.Get))); + + Assert.Equal(result, collection); + } + + [Fact] + public async Task GetCollectionsCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = (IList) new List(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var getCollectionsQuery = new GetCollectionsQuery(); + var collectionList = await collectionService.GetCollectionsAsync(getCollectionsQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == "/api/v4/collections/" + && req.Query == getCollectionsQuery + && req.HTTPMethod == HttpMethod.Get))); + + Assert.Equal(result, collectionList); + } + + [Fact] + public async Task GetMediaAsyncCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = (IList) new List(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var getMediaQuery = new GetMediaQuery("collectionId"); + var mediaIds = await collectionService.GetMediaAsync(getMediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == $"/api/v4/collections/{getMediaQuery.CollectionId}/media/" + && req.HTTPMethod == HttpMethod.Get))); + + Assert.Equal(result, mediaIds); + } + + [Fact] + public async Task AddMediaAsyncCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var addMediaQuery = new AddMediaQuery("collectionId", new List()); + await collectionService.AddMediaAsync(addMediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/collections/{addMediaQuery.CollectionId}/media/" + && req.Query == addMediaQuery + && req.HTTPMethod == HttpMethod.Post + && req.DeserializeResponse == false))); + } + + [Fact] + public async Task RemoveMediaAsyncCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var removeMediaQuery = new RemoveMediaQuery("collectionId", new List()); + await collectionService.RemoveMediaAsync(removeMediaQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/collections/{removeMediaQuery.CollectionId}/media/" + && req.Query == removeMediaQuery + && req.HTTPMethod == HttpMethod.Delete))); + } + + [Fact] + public async Task ShareCollectionAsyncCallsRequestSenderWithValidRequest() + { + var apiRequestSender = new Mock(); + var result = ""; + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var collectionService = new CollectionService(apiRequestSender.Object); + var shareQuery = new ShareQuery("collectionId", new List(), SharingPermission.View); + await collectionService.ShareCollectionAsync(shareQuery); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == $"/api/v4/collections/{shareQuery.CollectionId}/share/" + && req.Query == shareQuery + && req.HTTPMethod == HttpMethod.Post + && req.DeserializeResponse == false))); + } + } +} diff --git a/Bynder/Test/Service/OAuth/OAuthServiceTest.cs b/Bynder/Test/Service/OAuth/OAuthServiceTest.cs new file mode 100644 index 0000000..d100abf --- /dev/null +++ b/Bynder/Test/Service/OAuth/OAuthServiceTest.cs @@ -0,0 +1,71 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Bynder.Sdk.Api.Requests; +using Bynder.Sdk.Api.RequestSender; +using Bynder.Sdk.Model; +using Bynder.Sdk.Service.OAuth; +using Bynder.Sdk.Settings; +using Moq; +using Xunit; + +namespace Bynder.Test.Service.OAuth +{ + public class OAuthServiceTest + { + [Fact] + public void GetAuthorisationUrlReturnsCorrectUrl() + { + + OAuthService oauth = new OAuthService( + new Configuration { + BaseUrl = new Uri("https://example.bynder.com"), + ClientId = "clientId", + ClientSecret = "clientSecret", + RedirectUri = "https://redirect.bynder.com" + }, null, null); + + var authorisationUrl = oauth.GetAuthorisationUrl("state example"); + + Assert.Equal("https://example.bynder.com:443/v6/authentication/oauth2/auth?client_id=clientId&redirect_uri=https%3A%2F%2Fredirect.bynder.com&response_type=code&scope=openid%20offline&state=state%20example", + authorisationUrl); + } + + [Fact] + public async Task GetAccessTokenCallsRequestAsyncAndUpdates() + { + var apiRequestSender = new Mock(); + var result = new Token(); + var credentials = new Mock(); + apiRequestSender.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .Returns(Task.FromResult(result)); + var oauthService = new OAuthService( + new Configuration { + BaseUrl = new Uri("https://example.bynder.com"), + ClientId = "clientId", + ClientSecret = "clientSecret", + RedirectUri = "https://redirect.bynder.com" + }, + credentials.Object, + apiRequestSender.Object); + + await oauthService.GetAccessTokenAsync("code").ConfigureAwait(false); + + apiRequestSender.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == "/v6/authentication/oauth2/token" + && req.HTTPMethod == HttpMethod.Post + && !req.Authenticated + && req.Query != null + ))); + + credentials.Verify(cred => cred.Update( + It.Is( + token => token == result + ))); + } + } +} diff --git a/Bynder/Test/Settings/CredentialsTest.cs b/Bynder/Test/Settings/CredentialsTest.cs new file mode 100644 index 0000000..1139bae --- /dev/null +++ b/Bynder/Test/Settings/CredentialsTest.cs @@ -0,0 +1,54 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Model; +using Bynder.Sdk.Settings; +using Moq; +using Xunit; + +namespace Bynder.Test.Settings +{ + public class CredentialsTest + { + [Fact] + public void ValidReturnsTrueIfValidToken() + { + var token = new Token() { + AccessToken = string.Empty, + ExpiresIn = 45 + }; + token.SetAccessTokenExpiration(); + + Credentials credentials = new Credentials(token); + Assert.True(credentials.AreValid()); + } + + [Fact] + public void ValidReturnsFalseIfItExpiresInLessThan15Seconds() + { + var token = new Token() { + AccessToken = string.Empty, + ExpiresIn = 10 + }; + token.SetAccessTokenExpiration(); + + Credentials credentials = new Credentials(token); + Assert.False(credentials.AreValid()); + } + + + [Fact] + public void ValidReturnsFalseIfTokenAlreadyExpired() + { + var token = new Token() { + AccessToken = string.Empty, + ExpiresIn = -5 + }; + token.SetAccessTokenExpiration(); + + Credentials credentials = new Credentials(token); + Assert.False(credentials.AreValid()); + } + } +} diff --git a/Bynder/Test/Utils/HttpListenerFactory.cs b/Bynder/Test/Utils/HttpListenerFactory.cs new file mode 100644 index 0000000..880f6a5 --- /dev/null +++ b/Bynder/Test/Utils/HttpListenerFactory.cs @@ -0,0 +1,220 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +// Class copied from +// https://github.com/dotnet/corefx/blob/master/src/System.Net.HttpListener/tests/HttpListenerFactory.cs +// to be able to use multiple listeners when running tests parallel +namespace Bynder.Test.Utils +{ + // Utilities for generating URL prefixes for HttpListener + public class HttpListenerFactory : IDisposable + { + const int StartPort = 1025; + const int MaxStartAttempts = IPEndPoint.MaxPort - StartPort + 1; + private static readonly object s_nextPortLock = new object(); + private static int s_nextPort = StartPort; + + private readonly HttpListener _processPrefixListener; + private readonly Exception _processPrefixException; + private readonly string _processPrefix; + private readonly string _hostname; + private readonly string _path; + private readonly int _port; + + internal HttpListenerFactory(string hostname = "localhost", string path = null) + { + // Find a URL prefix that is not in use on this machine *and* uses a port that's not in use. + // Once we find this prefix, keep a listener on it for the duration of the process, so other processes + // can't steal it. + _hostname = hostname; + _path = path ?? Guid.NewGuid().ToString("N"); + string pathComponent = string.IsNullOrEmpty(_path) ? _path : $"{_path}/"; + + for (int attempt = 0; attempt < MaxStartAttempts; attempt++) + { + int port = GetNextPort(); + string prefix = $"http://{hostname}:{port}/{pathComponent}"; + + var listener = new HttpListener(); + try + { + listener.Prefixes.Add(prefix); + listener.Start(); + + _processPrefixListener = listener; + _processPrefix = prefix; + _port = port; + break; + } + catch (Exception e) + { + // can't use this prefix + listener.Close(); + + // Remember the exception for later + _processPrefixException = e; + + if (e is HttpListenerException listenerException) + { + // If we can't access the host (e.g. if it is '+' or '*' and the current user is the administrator) + // then throw. + const int ERROR_ACCESS_DENIED = 5; + if (listenerException.ErrorCode == ERROR_ACCESS_DENIED && (hostname == "*" || hostname == "+")) + { + throw new InvalidOperationException($"Access denied for host {hostname}"); + } + } + else if (!(e is SocketException)) + { + // If this is not an HttpListenerException or SocketException, something very wrong has happened, and there's no point + // in trying again. + break; + } + } + } + + // At this point, either we've reserved a prefix, or we've tried everything and failed. If we failed, + // we've saved the exception for later. We'll defer actually *throwing* the exception until a test + // asks for the prefix, because dealing with a type initialization exception is not nice in xunit. + } + + public int Port + { + get + { + if (_port == 0) + { + throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + } + + return _port; + } + } + + public string ListeningUrl + { + get + { + if (_processPrefix == null) + { + throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + } + + return _processPrefix; + } + } + + public string Hostname => _hostname; + public string Path => _path; + + private static bool? s_supportsWildcards; + public static bool SupportsWildcards + { + get + { + if (!s_supportsWildcards.HasValue) + { + try + { + using (new HttpListenerFactory("*")) + { + s_supportsWildcards = true; + } + } + catch (InvalidOperationException) + { + s_supportsWildcards = false; + } + } + + return s_supportsWildcards.Value; + } + } + + public HttpListener GetListener() => _processPrefixListener ?? throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + + public void Dispose() => _processPrefixListener?.Close(); + + public Socket GetConnectedSocket() + { + // Some platforms or distributions require IPv6 sockets if the OS supports IPv6. Others (e.g. Ubuntu) don't. + try + { + AddressFamily addressFamily = Socket.OSSupportsIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; + Socket socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(Hostname, _port); + return socket; + } + catch + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(Hostname, _port); + return socket; + } + } + + public byte[] GetContent(string httpVersion, string requestType, string query, string text, IEnumerable headers, bool headerOnly) + { + headers = headers ?? Enumerable.Empty(); + + Uri listeningUri = new Uri(ListeningUrl); + string rawUrl = listeningUri.PathAndQuery; + if (query != null) + { + rawUrl += query; + } + + string content = $"{requestType} {rawUrl} HTTP/{httpVersion}\r\n"; + if (!headers.Any(header => header.ToLower().StartsWith("host:"))) + { + content += $"Host: { listeningUri.Host}\r\n"; + } + if (text != null && !headers.Any(header => header.ToLower().StartsWith("content-length:"))) + { + content += $"Content-Length: {text.Length}\r\n"; + } + foreach (string header in headers) + { + content += header + "\r\n"; + } + content += "\r\n"; + + if (!headerOnly && text != null) + { + content += text; + } + + return Encoding.UTF8.GetBytes(content); + } + + public byte[] GetContent(string requestType, string text, bool headerOnly) + { + return GetContent("1.1", requestType, query: null, text: text, headers: null, headerOnly: headerOnly); + } + + private static int GetNextPort() + { + lock (s_nextPortLock) + { + int port = s_nextPort++; + if (s_nextPort > IPEndPoint.MaxPort) + { + s_nextPort = StartPort; + } + return port; + } + } + } + + public static class RequestTypes + { + public const string POST = "POST"; + } +} diff --git a/Bynder/Test/Utils/HttpListenerRequestEventArgs.cs b/Bynder/Test/Utils/HttpListenerRequestEventArgs.cs index dffc238..230e550 100644 --- a/Bynder/Test/Utils/HttpListenerRequestEventArgs.cs +++ b/Bynder/Test/Utils/HttpListenerRequestEventArgs.cs @@ -7,12 +7,12 @@ namespace Bynder.Test.Utils { /// - /// Event arguments that have request information done to the HttpListener + /// Event arguments that have request information done to the HttpListener. /// public class HttpListenerRequestEventArgs : EventArgs { /// - /// Constructor for the class + /// Constructor for the class. /// /// HttpListenerRequest instance public HttpListenerRequestEventArgs(HttpListenerRequest request) @@ -21,7 +21,7 @@ public HttpListenerRequestEventArgs(HttpListenerRequest request) } /// - /// Gets the Request information done to the HttpListener + /// Gets the Request information done to the HttpListener. /// public HttpListenerRequest Request { get; private set; } } diff --git a/Bynder/Test/Utils/TestHttpListener.cs b/Bynder/Test/Utils/TestHttpListener.cs index 0376c19..f1ba166 100644 --- a/Bynder/Test/Utils/TestHttpListener.cs +++ b/Bynder/Test/Utils/TestHttpListener.cs @@ -10,15 +10,15 @@ namespace Bynder.Test.Utils { /// /// Helper class to mock server responses. - /// It answers to requests with the content specified in file - /// It uses + /// It answers to requests with the content specified in file. + /// It uses . /// public sealed class TestHttpListener : IDisposable { /// - /// HttpListener to listen to specific Url + /// HttpListenerFactory to create listeners to specific Url. /// - private readonly HttpListener _httpListener; + private readonly HttpListenerFactory _httpListenerFactory; /// /// Path of the file which contents will be the response to requests. @@ -27,26 +27,27 @@ public sealed class TestHttpListener : IDisposable /// /// Status code we want to answer to requests. This helps to simulate - /// HTTP error codes + /// HTTP error codes. /// private readonly HttpStatusCode _statusCode; + + /// /// Creates an instance of the class. /// - /// Url we start listening to /// HTTP status code that the class will return when having requests /// File whose contents the class will return in the response - public TestHttpListener(string url, HttpStatusCode statusCode, string responsePath) + public TestHttpListener(HttpStatusCode statusCode, string responsePath) { _responsePath = responsePath; _statusCode = statusCode; - _httpListener = new HttpListener(); - _httpListener.Prefixes.Add(url); - _httpListener.Start(); + _httpListenerFactory = new HttpListenerFactory(); - _httpListener.BeginGetContext(new AsyncCallback(BeginContextCallback), _httpListener); + _httpListenerFactory + .GetListener() + .BeginGetContext(new AsyncCallback(BeginContextCallback), _httpListenerFactory.GetListener()); } /// @@ -55,16 +56,25 @@ public TestHttpListener(string url, HttpStatusCode statusCode, string responsePa /// public event EventHandler MessageReceived; + + public string ListeningUrl + { + get + { + return _httpListenerFactory.ListeningUrl; + } + } + /// - /// Disposes the httpListener + /// Disposes the _httpListenerFactory. /// public void Dispose() { - _httpListener.Close(); + _httpListenerFactory.Dispose(); } /// - /// Function called when we start receiving a request + /// Function called when we start receiving a request. /// /// Async result private void BeginContextCallback(IAsyncResult result) @@ -86,10 +96,10 @@ private void BeginContextCallback(IAsyncResult result) private void SendResponse(HttpListenerResponse response) { response.StatusCode = (int)_statusCode; - var dr = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!string.IsNullOrEmpty(_responsePath)) { + var dr = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); using (var fs = new FileStream(Path.Combine(dr, _responsePath), FileMode.Open, FileAccess.Read)) { fs.CopyTo(response.OutputStream); diff --git a/Bynder/Test/Utils/TestSettings.cs b/Bynder/Test/Utils/TestSettings.cs deleted file mode 100644 index 0751832..0000000 --- a/Bynder/Test/Utils/TestSettings.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Bynder. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. - -using System.Configuration; -using Bynder.Api; -using Bynder.Api.Impl.Oauth; - -namespace Bynder.Test.Utils -{ - /// - /// Mock data for test configuration used in different tests. - /// - internal static class TestConfiguration - { - /// - /// Gets Test Settings - /// - /// settings to use in tests - public static Settings GetSettings() - { - return new Settings - { - URL = "http://localhost:8890/", - TOKEN = "LOGIN_TOKEN", - TOKEN_SECRET = "LOGIN_TOKEN_SECRET", - CONSUMER_KEY = "CONSUMER_KEY", - CONSUMER_SECRET = "CONSUMER_SECRET" - }; - } - - /// - /// Gets Test Credentials - /// - /// credentials to use in test - public static Credentials GetCredentials() - { - var settings = GetSettings(); - return new Credentials(settings.CONSUMER_KEY, settings.CONSUMER_SECRET, settings.TOKEN, settings.TOKEN_SECRET); - } - } -} diff --git a/Bynder/Test/Utils/UrlTest.cs b/Bynder/Test/Utils/UrlTest.cs new file mode 100644 index 0000000..a9d408e --- /dev/null +++ b/Bynder/Test/Utils/UrlTest.cs @@ -0,0 +1,31 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Bynder.Sdk.Utils; +using Xunit; + +namespace Bynder.Test.Utils +{ + public class UrlTest + { + [Fact] + public void ParametersAreConvertedToValidQueryUrl() + { + var dictionary = new Dictionary + { + { "item1", "\"value'" }, + { "item2", "value2" } + }; + + var queryUri = Url.ConvertToQuery(dictionary); + Assert.Equal("item1=%22value%27&item2=value2", queryUri); + } + + [Fact] + public void IfEmptyDictionaryPassedNothingIsReturned() + { + Assert.Empty(Url.ConvertToQuery(new Dictionary())); + } + } +} diff --git a/Makefile b/Makefile index 08fc098..33bdda2 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,8 @@ test: clean: rm -rf \ - Bynder/Api/bin \ - Bynder/Api/obj \ - Bynder/Models/bin \ - Bynder/Models/obj \ + Bynder/Sdk/bin \ + Bynder/Sdk/obj \ Bynder/Sample/bin \ Bynder/Sample/obj \ Bynder/Test/bin \ diff --git a/README.md b/README.md index 00c71b1..237039d 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,38 @@ # Bynder C# SDK + [![Build Status](https://travis-ci.org/Bynder/bynder-c-sharp-sdk.svg?branch=master)](https://travis-ci.org/Bynder/bynder-c-sharp-sdk) The main goal of this SDK is to speed up the integration of Bynder customers who use C# making it easier to connect to the Bynder API (http://docs.bynder.apiary.io) and executing requests on it. ## Nuget Package + You can download and use Bynder SDK from Nuget. https://www.nuget.org/packages/Bynder.Sdk/ ## Current status At the moment this SDK provides a library with the following methods: -### Login +### OAuth operations + ```c# -Task LoginAsync(string email, string password); -Task GetAccessTokenAsync(); -Task GetRequestTokenAsync(); -string GetAuthorizeUrl(string callbackUrl); -void Logout(); +string GetAuthorisationUrl(string state, string scopes); +Task GetAccessTokenAsync(string code, string scopes); ``` + ### Asset management operations + ```c# Task> GetBrandsAsync(); Task GetDownloadFileUrlAsync(DownloadMediaQuery query); Task> GetMetapropertiesAsync(); -Task RequestMediaInfoAsync(MediaInformationQuery query); -Task> RequestMediaListAsync(MediaQuery query); +Task GetMediaInfoAsync(MediaInformationQuery query); +Task> GetMediaListAsync(MediaQuery query); Task UploadFileAsync(UploadQuery query); Task ModifyMediaAsync(ModifyMediaQuery query); ``` + ### Collection management operations + ```c# Task> GetCollectionsAsync(GetCollectionsQuery query); Task GetCollectionAsync(string id); diff --git a/Settings.StyleCop b/Settings.StyleCop deleted file mode 100644 index d314c24..0000000 --- a/Settings.StyleCop +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/byndercsharpsdk.sln b/byndercsharpsdk.sln index 25f894b..fe14626 100644 --- a/byndercsharpsdk.sln +++ b/byndercsharpsdk.sln @@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bynder.Api", "Bynder\Api\Bynder.Api.csproj", "{3C667FDB-9143-4F33-8E16-6AFBEFCC335A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bynder.Models", "Bynder\Models\Bynder.Models.csproj", "{6D0798E3-CD6F-40E5-9BBB-838A69BD84F7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bynder.Sdk", "Bynder\Sdk\Bynder.Sdk.csproj", "{3C667FDB-9143-4F33-8E16-6AFBEFCC335A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bynder.Sample", "Bynder\Sample\Bynder.Sample.csproj", "{29FFFA5A-7215-49C4-8C41-81FD6A6A7857}" EndProject @@ -21,10 +19,6 @@ Global {3C667FDB-9143-4F33-8E16-6AFBEFCC335A}.Debug|Any CPU.Build.0 = Debug|Any CPU {3C667FDB-9143-4F33-8E16-6AFBEFCC335A}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C667FDB-9143-4F33-8E16-6AFBEFCC335A}.Release|Any CPU.Build.0 = Release|Any CPU - {6D0798E3-CD6F-40E5-9BBB-838A69BD84F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D0798E3-CD6F-40E5-9BBB-838A69BD84F7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D0798E3-CD6F-40E5-9BBB-838A69BD84F7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D0798E3-CD6F-40E5-9BBB-838A69BD84F7}.Release|Any CPU.Build.0 = Release|Any CPU {29FFFA5A-7215-49C4-8C41-81FD6A6A7857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29FFFA5A-7215-49C4-8C41-81FD6A6A7857}.Debug|Any CPU.Build.0 = Debug|Any CPU {29FFFA5A-7215-49C4-8C41-81FD6A6A7857}.Release|Any CPU.ActiveCfg = Release|Any CPU