using System; using System.Collections.Generic; using System.Linq; using System.Web; using Newtonsoft.Json; using System.Net.Http.Formatting; using CPE.App.Web.Models; using System.Net; using System.Security.Cryptography; using System.Text; using System.Reflection; using CPE.App.Web.Code; namespace CPE.App.Web.Elucidat { public class EludicatClient { private readonly string _publicKey; private readonly string _secretKey; private readonly bool _simulationMode; private readonly Uri _baseUrl; private readonly JsonMediaTypeFormatter[] _jsonFormatters = { new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new UnderscoreContractResolver(), } } }; /// /// Create and configure API client service /// /// /// /// /// public EludicatClient(string publicKey, string secretKey, bool simulationMode, string baseUrl) { this._publicKey = publicKey; this._secretKey = secretKey; this._simulationMode = simulationMode; this._baseUrl = new Uri(baseUrl); } /// /// Call a GET API method returning a structure of type T without URL parameters /// /// /// /// private T Get(string url) { return Get(url, new Dictionary()); } /// /// Call a GET API method returning a structure of type T with URL parameters /// /// /// /// /// public T Get(string url, IDictionary fields) { //string fieldsFirstKeyValue = String.Empty; //if (fields.Count > 0) //{ // fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value; //} //Extensions.LogServiceCall("[EludicatClient][Get]", String.Format("url = {0} fieldsFirstKeyValue = {1} _simulationMode = {2}", url, fieldsFirstKeyValue, _simulationMode)); if (_simulationMode) fields = fields.Concat(new Dictionary { { "simulation_mode", "simulation" } }) .ToDictionary(x => x.Key, x => x.Value); return CallGet(AuthHeaders(GetNonce(url)), fields, url); } /// /// Internal get-based API call. Used for both actual API call and obtaining nonces. /// /// /// /// /// /// private T CallGet(IDictionary headers, IDictionary fields, string url) { T result = default(T); string fieldsFirstKeyValue = String.Empty; //if (fields.Count > 0) //{ // fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value; //} //Extensions.LogServiceCall("[EludicatClient][CallGet]", String.Format("fields.Count = {0} fieldsFirstKeyValue = {1}", fields.Count, fieldsFirstKeyValue)); var signedHeaders = headers.Concat(new Dictionary { {"oauth_signature", Sign(headers.Concat(fields), url, "GET")} }); // Remove insecure protocols (SSL3, TLS 1.0, TLS 1.1) ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3; ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls; ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls11; // Add TLS 1.2 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; var client = new WebClient { BaseAddress = _baseUrl.ToString() }; client.Headers.Clear(); client.Headers["Accept"] = "application/json"; client.Headers.Add("Authorization", BuildBaseString(signedHeaders, ",")); var urlFull = url + "?" + BuildBaseString(fields, "&"); var response = client.DownloadString(urlFull); try { result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings); } catch (Exception exception) { if (fields.Count > 0) { fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value; } Extensions.LogServiceCall("[EludicatClient][CallGet]", String.Format("urlFull = {0} typeof(T) = {1} fields.Count = {2} fieldsFirstKeyValue = {3} _baseUrl = {4} response = {5}", urlFull, typeof(T).ToString(), fields.Count, fieldsFirstKeyValue, _baseUrl, response)); Extensions.LogServiceError("[EludicatClient][CallGet]", exception); throw; } return result; } /// /// Call a POST-based API method with parameters /// /// /// /// /// public T Post(string url, IDictionary fields) { if (_simulationMode) fields = fields.Concat(new Dictionary { { "simulation_mode", "simulation" } }) .ToDictionary(x => x.Key, x => x.Value); return CallPost(AuthHeaders(GetNonce(url)), fields, url); } /// /// Internal POST call. Used only for actual API method invocations. /// /// /// /// /// /// private T CallPost(IDictionary headers, IDictionary fields, string url) { var signedHeaders = headers.Concat(new Dictionary { {"oauth_signature", Sign(headers.Concat(fields), url, "POST")} }); // Remove insecure protocols (SSL3, TLS 1.0, TLS 1.1) ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3; ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls; ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls11; // Add TLS 1.2 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; var client = new WebClient(); client.BaseAddress = _baseUrl.ToString(); client.Headers.Clear(); client.Headers["Accept"] = "application/json"; client.Headers["Content-Type"] = "application/x-www-form-urlencoded"; client.Headers.Add("Authorization", BuildBaseString(signedHeaders, ",")); client.Headers.Add("Expect", ""); var content = BuildBaseString(fields, "&"); try { var response = client.UploadString(url, content); var result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings); return result; } catch (WebException e) { throw; } } /// /// Generate an HMAC signature for an API request /// /// /// /// /// private string Sign(IEnumerable> parameters, string url, string httpMethod) { var baseString = httpMethod + "&" + new Uri(_baseUrl, url) + "&" + BuildBaseString(parameters.OrderBy(x => x.Key), "&"); var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(_secretKey.RawUrlEncode())); hmac.Initialize(); return Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(baseString))); } /// /// Create a query-string like single string from a parameter dictionary. /// /// /// /// private string BuildBaseString(IEnumerable> parameters, string delimiter) { return string.Join(delimiter, parameters.Select(x => x.Key.RawUrlEncode() + "=" + x.Value.RawUrlEncode())); } /// /// Create a parameter dictionary from an object /// /// ///// //private IDictionary BuildParameterDictionary(object o) //{ // IDictionary dict = o.GetType() // .FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null) // .Where(p => (((PropertyInfo)p).GetValue(o)) != null) // .ToDictionary(propertyInfo => propertyInfo.Name.TransformPropertyName(), propertyInfo => Convert.ToString(((PropertyInfo)propertyInfo).GetValue(o))); // return dict; //} /// /// Base authentication headers required for every API request. /// /// /// private IDictionary AuthHeaders(string nonce) { var headers = new Dictionary { {"oauth_consumer_key", _publicKey}, {"oauth_signature_method", "HMAC-SHA1"}, { "oauth_timestamp", Math.Floor( DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds) .ToString() }, {"oauth_version", "1.0"} }; if (nonce != null) headers.Add("oauth_nonce", nonce); return headers; } /// /// Retrieve a security nonce from the API to authenticate the next method call. /// /// /// private string GetNonce(string url) { string returnValue = null; var response = CallGet(AuthHeaders(null), new Dictionary(), url); if (response != null) { returnValue = response.Nonce; } return returnValue; } /// /// Retrieve the details of a single release /// /// /// public ReleaseModel GetRelease(string releaseCode) { return Get("releases/details", new Dictionary { { "release_code", releaseCode } }); } /// /// Retrieve a link which can be used to launch the given release. /// /// /// public LinkModel GetLaunchLink(string releaseCode) { return Get("releases/launch", new Dictionary { {"release_code", releaseCode} }); } /// /// Retrieve a link which can be used to launch the given release. /// /// /// /// /// /// /// public LinkModel GetLaunchLink(string releaseCode, string firstname, string lastname, string email) { return Get("releases/launch", new Dictionary { { "release_code", releaseCode}, { "name", firstname + " " + lastname}, { "email_address", email} }); } public MessageModel ConfigureProject(ProjectSettingModel settings) { Extensions.LogServiceCall("[EludicatClient][ConfigureProject]", String.Format("ProjectCode = {0}", settings.ProjectCode)); return Post("projects/configure", BuildParameterDictionary(settings)); } public MessageModel CreateRelease(ReleaseSettingModel settings) { Extensions.LogServiceCall("[EludicatClient][CreateRelease]", String.Format("ReleaseCode = {0} ProjectCode = {1} Description = {2}", settings.ReleaseCode, settings.ProjectCode, settings.Description)); return Post("releases/create", BuildParameterDictionary(settings)); } /// /// Create a parameter dictionary from an object /// /// /// private IDictionary BuildParameterDictionary(object o) { IDictionary dict = o.GetType() .FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null) .Where(p => (((PropertyInfo)p).GetValue(o)) != null) .ToDictionary(propertyInfo => propertyInfo.Name.TransformPropertyName(), propertyInfo => Convert.ToString(((PropertyInfo)propertyInfo).GetValue(o))); return dict; } } }