2 using System.Collections.Generic;
6 using System.Net.Http.Formatting;
7 using CPE.App.Web.Models;
9 using System.Security.Cryptography;
11 using System.Reflection;
12 using CPE.App.Web.Code;
14 namespace CPE.App.Web.Elucidat
16 public class EludicatClient
18 private readonly string _publicKey;
19 private readonly string _secretKey;
20 private readonly bool _simulationMode;
21 private readonly Uri _baseUrl;
23 private readonly JsonMediaTypeFormatter[] _jsonFormatters =
25 new JsonMediaTypeFormatter
27 SerializerSettings = new JsonSerializerSettings
29 ContractResolver = new UnderscoreContractResolver(),
35 /// Create and configure API client service
37 /// <param name="publicKey"></param>
38 /// <param name="secretKey"></param>
39 /// <param name="simulationMode"></param>
40 /// <param name="baseUrl"></param>
41 public EludicatClient(string publicKey, string secretKey, bool simulationMode, string baseUrl)
43 this._publicKey = publicKey;
44 this._secretKey = secretKey;
45 this._simulationMode = simulationMode;
46 this._baseUrl = new Uri(baseUrl);
50 /// Call a GET API method returning a structure of type T without URL parameters
52 /// <typeparam name="T"></typeparam>
53 /// <param name="url"></param>
54 /// <returns></returns>
55 private T Get<T>(string url)
57 return Get<T>(url, new Dictionary<string, string>());
61 /// Call a GET API method returning a structure of type T with URL parameters
63 /// <typeparam name="T"></typeparam>
64 /// <param name="url"></param>
65 /// <param name="fields"></param>
66 /// <returns></returns>
67 public T Get<T>(string url, IDictionary<string, string> fields)
69 //string fieldsFirstKeyValue = String.Empty;
70 //if (fields.Count > 0)
72 // fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
74 //Extensions.LogServiceCall("[EludicatClient][Get]", String.Format("url = {0} fieldsFirstKeyValue = {1} _simulationMode = {2}", url, fieldsFirstKeyValue, _simulationMode));
78 fields.Concat(new Dictionary<string, string> { { "simulation_mode", "simulation" } })
79 .ToDictionary(x => x.Key, x => x.Value);
81 return CallGet<T>(AuthHeaders(GetNonce(url)), fields, url);
86 /// Internal get-based API call. Used for both actual API call and obtaining nonces.
88 /// <typeparam name="T"></typeparam>
89 /// <param name="headers"></param>
90 /// <param name="fields"></param>
91 /// <param name="url"></param>
92 /// <returns></returns>
93 private T CallGet<T>(IDictionary<string, string> headers, IDictionary<string, string> fields, string url)
95 T result = default(T);
97 string fieldsFirstKeyValue = String.Empty;
98 //if (fields.Count > 0)
100 // fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
102 //Extensions.LogServiceCall("[EludicatClient][CallGet]", String.Format("fields.Count = {0} fieldsFirstKeyValue = {1}", fields.Count, fieldsFirstKeyValue));
105 headers.Concat(new Dictionary<string, string>
107 {"oauth_signature", Sign(headers.Concat(fields), url, "GET")}
110 // Remove insecure protocols (SSL3, TLS 1.0, TLS 1.1)
111 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
112 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls;
113 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls11;
115 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
118 var client = new WebClient { BaseAddress = _baseUrl.ToString() };
119 client.Headers.Clear();
120 client.Headers["Accept"] = "application/json";
121 client.Headers.Add("Authorization", BuildBaseString(signedHeaders, ","));
122 var urlFull = url + "?" + BuildBaseString(fields, "&");
123 var response = client.DownloadString(urlFull);
126 result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings);
128 catch (Exception exception)
130 if (fields.Count > 0)
132 fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
134 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));
135 Extensions.LogServiceError("[EludicatClient][CallGet]", exception);
142 /// Call a POST-based API method with parameters
144 /// <typeparam name="T"></typeparam>
145 /// <param name="url"></param>
146 /// <param name="fields"></param>
147 /// <returns></returns>
148 public T Post<T>(string url, IDictionary<string, string> fields)
152 fields.Concat(new Dictionary<string, string> { { "simulation_mode", "simulation" } })
153 .ToDictionary(x => x.Key, x => x.Value);
155 return CallPost<T>(AuthHeaders(GetNonce(url)), fields, url);
159 /// Internal POST call. Used only for actual API method invocations.
161 /// <typeparam name="T"></typeparam>
162 /// <param name="headers"></param>
163 /// <param name="fields"></param>
164 /// <param name="url"></param>
165 /// <returns></returns>
166 private T CallPost<T>(IDictionary<string, string> headers, IDictionary<string, string> fields, string url)
169 headers.Concat(new Dictionary<string, string>
171 {"oauth_signature", Sign(headers.Concat(fields), url, "POST")}
174 // Remove insecure protocols (SSL3, TLS 1.0, TLS 1.1)
175 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
176 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls;
177 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls11;
179 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
182 var client = new WebClient();
183 client.BaseAddress = _baseUrl.ToString();
184 client.Headers.Clear();
185 client.Headers["Accept"] = "application/json";
186 client.Headers["Content-Type"] = "application/x-www-form-urlencoded";
187 client.Headers.Add("Authorization", BuildBaseString(signedHeaders, ","));
188 client.Headers.Add("Expect", "");
189 var content = BuildBaseString(fields, "&");
192 var response = client.UploadString(url, content);
193 var result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings);
196 catch (WebException e)
205 /// Generate an HMAC signature for an API request
207 /// <param name="parameters"></param>
208 /// <param name="url"></param>
209 /// <param name="httpMethod"></param>
210 /// <returns></returns>
211 private string Sign(IEnumerable<KeyValuePair<string, string>> parameters, string url, string httpMethod)
213 var baseString = httpMethod + "&" + new Uri(_baseUrl, url) + "&" +
214 BuildBaseString(parameters.OrderBy(x => x.Key), "&");
216 var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(_secretKey.RawUrlEncode()));
219 return Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(baseString)));
223 /// Create a query-string like single string from a parameter dictionary.
225 /// <param name="parameters"></param>
226 /// <param name="delimiter"></param>
227 /// <returns></returns>
228 private string BuildBaseString(IEnumerable<KeyValuePair<string, string>> parameters, string delimiter)
230 return string.Join(delimiter, parameters.Select(x => x.Key.RawUrlEncode() + "=" + x.Value.RawUrlEncode()));
234 /// Create a parameter dictionary from an object
236 /// <param name="o"></param>
237 ///// <returns></returns>
238 //private IDictionary<string, string> BuildParameterDictionary(object o)
240 // IDictionary<string, string> dict = o.GetType()
241 // .FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null)
242 // .Where(p => (((PropertyInfo)p).GetValue(o)) != null)
243 // .ToDictionary(propertyInfo => propertyInfo.Name.TransformPropertyName(), propertyInfo => Convert.ToString(((PropertyInfo)propertyInfo).GetValue(o)));
248 /// Base authentication headers required for every API request.
250 /// <param name="nonce"></param>
251 /// <returns></returns>
252 private IDictionary<string, string> AuthHeaders(string nonce)
254 var headers = new Dictionary<string, string>
256 {"oauth_consumer_key", _publicKey},
257 {"oauth_signature_method", "HMAC-SHA1"},
261 DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds)
264 {"oauth_version", "1.0"}
268 headers.Add("oauth_nonce", nonce);
274 /// Retrieve a security nonce from the API to authenticate the next method call.
276 /// <param name="url"></param>
277 /// <returns></returns>
278 private string GetNonce(string url)
280 string returnValue = null;
281 var response = CallGet<NonceModel>(AuthHeaders(null), new Dictionary<string, string>(), url);
282 if (response != null)
284 returnValue = response.Nonce;
290 /// Retrieve the details of a single release
292 /// <param name="releaseCode"></param>
293 /// <returns></returns>
294 public ReleaseModel GetRelease(string releaseCode)
296 return Get<ReleaseModel>("releases/details", new Dictionary<string, string> { { "release_code", releaseCode } });
300 /// Retrieve a link which can be used to launch the given release.
302 /// <param name="releaseCode"></param>
303 /// <returns></returns>
304 public LinkModel GetLaunchLink(string releaseCode)
306 return Get<LinkModel>("releases/launch", new Dictionary<string, string>
308 {"release_code", releaseCode}
313 /// Retrieve a link which can be used to launch the given release.
315 /// <param name="releaseCode"></param>
316 /// <param name="firstname"></param>
317 /// <param name="lastname"></param>
318 /// <param name="email"></param>
319 /// <param name="passcode"></param>
320 /// <returns></returns>
321 public LinkModel GetLaunchLink(string releaseCode, string firstname, string lastname, string email)
323 return Get<LinkModel>("releases/launch", new Dictionary<string, string>
325 { "release_code", releaseCode},
326 { "name", firstname + " " + lastname},
327 { "email_address", email}
330 public MessageModel ConfigureProject(ProjectSettingModel settings)
332 Extensions.LogServiceCall("[EludicatClient][ConfigureProject]", String.Format("ProjectCode = {0}", settings.ProjectCode));
334 return Post<MessageModel>("projects/configure", BuildParameterDictionary(settings));
336 public MessageModel CreateRelease(ReleaseSettingModel settings)
338 Extensions.LogServiceCall("[EludicatClient][CreateRelease]", String.Format("ReleaseCode = {0} ProjectCode = {1} Description = {2}", settings.ReleaseCode, settings.ProjectCode, settings.Description));
340 return Post<MessageModel>("releases/create", BuildParameterDictionary(settings));
344 /// Create a parameter dictionary from an object
346 /// <param name="o"></param>
347 /// <returns></returns>
348 private IDictionary<string, string> BuildParameterDictionary(object o)
350 IDictionary<string, string> dict = o.GetType()
351 .FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null)
352 .Where(p => (((PropertyInfo)p).GetValue(o)) != null)
353 .ToDictionary(propertyInfo => propertyInfo.Name.TransformPropertyName(), propertyInfo => Convert.ToString(((PropertyInfo)propertyInfo).GetValue(o)));