initial commit
[CPE_learningsite] / CPE / CPE.App / CPE.App.Web / Elucidat / EludicatClient.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using Newtonsoft.Json;
6 using System.Net.Http.Formatting;
7 using CPE.App.Web.Models;
8 using System.Net;
9 using System.Security.Cryptography;
10 using System.Text;
11 using System.Reflection;
12 using CPE.App.Web.Code;
13
14 namespace CPE.App.Web.Elucidat
15 {
16     public class EludicatClient
17     {
18         private readonly string _publicKey;
19         private readonly string _secretKey;
20         private readonly bool _simulationMode;
21         private readonly Uri _baseUrl;
22
23         private readonly JsonMediaTypeFormatter[] _jsonFormatters =
24         {
25             new JsonMediaTypeFormatter
26             {
27                 SerializerSettings = new JsonSerializerSettings
28                 {
29                     ContractResolver = new UnderscoreContractResolver(),
30                 }
31             }
32         };
33
34         /// <summary>
35         /// Create and configure API client service
36         /// </summary>
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)
42         {
43             this._publicKey = publicKey;
44             this._secretKey = secretKey;
45             this._simulationMode = simulationMode;
46             this._baseUrl = new Uri(baseUrl);
47         }
48
49         /// <summary>
50         /// Call a GET API method returning a structure of type T without URL parameters
51         /// </summary>
52         /// <typeparam name="T"></typeparam>
53         /// <param name="url"></param>
54         /// <returns></returns>
55         private T Get<T>(string url)
56         {
57             return Get<T>(url, new Dictionary<string, string>());
58         }
59
60         /// <summary>
61         /// Call a GET API method returning a structure of type T with URL parameters
62         /// </summary>
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)
68         {
69             //string fieldsFirstKeyValue = String.Empty;
70             //if (fields.Count > 0)
71             //{
72             //    fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
73             //}
74             //Extensions.LogServiceCall("[EludicatClient][Get]", String.Format("url = {0} fieldsFirstKeyValue = {1} _simulationMode = {2}", url, fieldsFirstKeyValue, _simulationMode));
75
76             if (_simulationMode)
77                 fields =
78                     fields.Concat(new Dictionary<string, string> { { "simulation_mode", "simulation" } })
79                         .ToDictionary(x => x.Key, x => x.Value);
80
81             return CallGet<T>(AuthHeaders(GetNonce(url)), fields, url);
82         }
83
84
85         /// <summary>
86         /// Internal get-based API call. Used for both actual API call and obtaining nonces.
87         /// </summary>
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)
94         {
95             T result = default(T);
96
97             string fieldsFirstKeyValue = String.Empty;
98             //if (fields.Count > 0)
99             //{
100             //    fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
101             //}
102             //Extensions.LogServiceCall("[EludicatClient][CallGet]", String.Format("fields.Count = {0} fieldsFirstKeyValue = {1}", fields.Count, fieldsFirstKeyValue));
103
104             var signedHeaders =
105                 headers.Concat(new Dictionary<string, string>
106                 {
107                     {"oauth_signature", Sign(headers.Concat(fields), url, "GET")}
108                 });
109
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;
114             // Add TLS 1.2
115             ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
116
117
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);
124             try
125             {
126                 result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings);
127             }
128             catch (Exception exception)
129             {
130                 if (fields.Count > 0)
131                 {
132                     fieldsFirstKeyValue = fields.First().Key + " " + fields.First().Value;
133                 }
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);
136                 throw;
137             }
138             return result;
139         }
140
141         /// <summary>
142         /// Call a POST-based API method with parameters
143         /// </summary>
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)
149         {
150             if (_simulationMode)
151                 fields =
152                     fields.Concat(new Dictionary<string, string> { { "simulation_mode", "simulation" } })
153                         .ToDictionary(x => x.Key, x => x.Value);
154
155             return CallPost<T>(AuthHeaders(GetNonce(url)), fields, url);
156         }
157
158         /// <summary>
159         /// Internal POST call. Used only for actual API method invocations.
160         /// </summary>
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)
167         {
168             var signedHeaders =
169                 headers.Concat(new Dictionary<string, string>
170                 {
171                     {"oauth_signature", Sign(headers.Concat(fields), url, "POST")}
172                 });
173
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;
178             // Add TLS 1.2
179             ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
180
181
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, "&");
190             try
191             {
192                 var response = client.UploadString(url, content);
193                 var result = (T)JsonConvert.DeserializeObject(response, typeof(T), _jsonFormatters[0].SerializerSettings);
194                 return result;
195             }
196             catch (WebException e)
197             {
198
199                 throw;
200             }
201
202         }
203
204         /// <summary>
205         /// Generate an HMAC signature for an API request
206         /// </summary>
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)
212         {
213             var baseString = httpMethod + "&" + new Uri(_baseUrl, url) + "&" +
214                              BuildBaseString(parameters.OrderBy(x => x.Key), "&");
215
216             var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(_secretKey.RawUrlEncode()));
217             hmac.Initialize();
218
219             return Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(baseString)));
220         }
221
222         /// <summary>
223         /// Create a query-string like single string from a parameter dictionary.
224         /// </summary>
225         /// <param name="parameters"></param>
226         /// <param name="delimiter"></param>
227         /// <returns></returns>
228         private string BuildBaseString(IEnumerable<KeyValuePair<string, string>> parameters, string delimiter)
229         {
230             return string.Join(delimiter, parameters.Select(x => x.Key.RawUrlEncode() + "=" + x.Value.RawUrlEncode()));
231         }
232
233         /// <summary>
234         /// Create a parameter dictionary from an object
235         /// </summary>
236         /// <param name="o"></param>
237         ///// <returns></returns>
238         //private IDictionary<string, string> BuildParameterDictionary(object o)
239         //{
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)));
244         //    return dict;
245         //}
246
247         /// <summary>
248         /// Base authentication headers required for every API request.
249         /// </summary>
250         /// <param name="nonce"></param>
251         /// <returns></returns>
252         private IDictionary<string, string> AuthHeaders(string nonce)
253         {
254             var headers = new Dictionary<string, string>
255             {
256                 {"oauth_consumer_key", _publicKey},
257                 {"oauth_signature_method", "HMAC-SHA1"},
258                 {
259                     "oauth_timestamp",
260                     Math.Floor(
261                         DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds)
262                         .ToString()
263                 },
264                 {"oauth_version", "1.0"}
265             };
266
267             if (nonce != null)
268                 headers.Add("oauth_nonce", nonce);
269
270             return headers;
271         }
272
273         /// <summary>
274         /// Retrieve a security nonce from the API to authenticate the next method call.
275         /// </summary>
276         /// <param name="url"></param>
277         /// <returns></returns>
278         private string GetNonce(string url)
279         {
280             string returnValue = null;
281             var response = CallGet<NonceModel>(AuthHeaders(null), new Dictionary<string, string>(), url);
282             if (response != null)
283             {
284                 returnValue = response.Nonce;
285             }
286             return returnValue;
287         }
288
289         /// <summary>
290         /// Retrieve the details of a single release
291         /// </summary>
292         /// <param name="releaseCode"></param>
293         /// <returns></returns>
294         public ReleaseModel GetRelease(string releaseCode)
295         {
296             return Get<ReleaseModel>("releases/details", new Dictionary<string, string> { { "release_code", releaseCode } });
297         }
298
299         /// <summary>
300         /// Retrieve a link which can be used to launch the given release.
301         /// </summary>
302         /// <param name="releaseCode"></param>
303         /// <returns></returns>
304         public LinkModel GetLaunchLink(string releaseCode)
305         {
306             return Get<LinkModel>("releases/launch", new Dictionary<string, string>
307             {
308                 {"release_code", releaseCode}
309             });
310         }
311
312         /// <summary>
313         /// Retrieve a link which can be used to launch the given release.
314         /// </summary>
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)
322         {
323             return Get<LinkModel>("releases/launch", new Dictionary<string, string>
324             {
325                 { "release_code", releaseCode},
326                 { "name", firstname + " " + lastname},
327                 { "email_address", email}
328             });
329         }
330         public MessageModel ConfigureProject(ProjectSettingModel settings)
331         {
332             Extensions.LogServiceCall("[EludicatClient][ConfigureProject]", String.Format("ProjectCode = {0}", settings.ProjectCode));
333
334             return Post<MessageModel>("projects/configure", BuildParameterDictionary(settings));
335         }
336         public MessageModel CreateRelease(ReleaseSettingModel settings)
337         {
338             Extensions.LogServiceCall("[EludicatClient][CreateRelease]", String.Format("ReleaseCode = {0} ProjectCode = {1} Description = {2}", settings.ReleaseCode, settings.ProjectCode, settings.Description));
339
340             return Post<MessageModel>("releases/create", BuildParameterDictionary(settings));
341         }
342
343         /// <summary>
344         /// Create a parameter dictionary from an object
345         /// </summary>
346         /// <param name="o"></param>
347         /// <returns></returns>
348         private IDictionary<string, string> BuildParameterDictionary(object o)
349         {
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)));
354             return dict;
355         }
356     }
357 }