PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Microsoft.Alm.Authentication/GitHubAuthority.cs

https://gitlab.com/mayakarya/Git-Credential-Manager-for-Windows
C# | 221 lines | 156 code | 30 blank | 35 comment | 14 complexity | 5ba5b9a59ab7759e65d446644976d4a2 MD5 | raw file
  1. /**** Git Credential Manager for Windows ****
  2. *
  3. * Copyright (c) Microsoft Corporation
  4. * All rights reserved.
  5. *
  6. * MIT License
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the """"Software""""), to deal
  10. * in the Software without restriction, including without limitation the rights to
  11. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  12. * the Software, and to permit persons to whom the Software is furnished to do so,
  13. * subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in all
  16. * copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  20. * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  21. * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  22. * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  23. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  24. **/
  25. using System;
  26. using System.Diagnostics;
  27. using System.Linq;
  28. using System.Net;
  29. using System.Net.Http;
  30. using System.Text;
  31. using System.Text.RegularExpressions;
  32. using System.Threading.Tasks;
  33. namespace Microsoft.Alm.Authentication
  34. {
  35. internal class GitHubAuthority : IGitHubAuthority
  36. {
  37. /// <summary>
  38. /// The GitHub authorizations URL
  39. /// </summary>
  40. public const string DefaultAuthorityUrl = "https://api.github.com/authorizations";
  41. /// <summary>
  42. /// The GitHub required HTTP accepts header value
  43. /// </summary>
  44. public const string GitHubApiAcceptsHeaderValue = "application/vnd.github.v3+json";
  45. /// <summary>
  46. /// The maximum wait time for a network request before timing out
  47. /// </summary>
  48. public const int RequestTimeout = 15 * 1000; // 15 second limit
  49. public GitHubAuthority(string authorityUrl = null)
  50. {
  51. _authorityUrl = authorityUrl ?? DefaultAuthorityUrl;
  52. }
  53. private readonly string _authorityUrl;
  54. public async Task<GitHubAuthenticationResult> AcquireToken(
  55. TargetUri targetUri,
  56. string username,
  57. string password,
  58. string authenticationCode,
  59. GitHubTokenScope scope)
  60. {
  61. const string GitHubOptHeader = "X-GitHub-OTP";
  62. Trace.WriteLine("GitHubAuthority::AcquireToken");
  63. Token token = null;
  64. using (HttpClientHandler handler = targetUri.HttpClientHandler)
  65. using (HttpClient httpClient = new HttpClient(handler)
  66. {
  67. Timeout = TimeSpan.FromMilliseconds(RequestTimeout)
  68. })
  69. {
  70. httpClient.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent);
  71. httpClient.DefaultRequestHeaders.Add("Accept", GitHubApiAcceptsHeaderValue);
  72. string basicAuthValue = String.Format("{0}:{1}", username, password);
  73. byte[] authBytes = Encoding.UTF8.GetBytes(basicAuthValue);
  74. basicAuthValue = Convert.ToBase64String(authBytes);
  75. httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + basicAuthValue);
  76. if (!String.IsNullOrWhiteSpace(authenticationCode))
  77. {
  78. httpClient.DefaultRequestHeaders.Add(GitHubOptHeader, authenticationCode);
  79. }
  80. const string HttpJsonContentType = "application/x-www-form-urlencoded";
  81. const string JsonContentFormat = @"{{ ""scopes"": {0}, ""note"": ""git: {1} on {2} at {3:dd-MMM-yyyy HH:mm}"" }}";
  82. StringBuilder scopesBuilder = new StringBuilder();
  83. scopesBuilder.Append('[');
  84. foreach (var item in scope.ToString().Split(' '))
  85. {
  86. scopesBuilder.Append("\"")
  87. .Append(item)
  88. .Append("\"")
  89. .Append(", ");
  90. }
  91. // remove trailing ", "
  92. if (scopesBuilder.Length > 0)
  93. {
  94. scopesBuilder.Remove(scopesBuilder.Length - 2, 2);
  95. }
  96. scopesBuilder.Append(']');
  97. string jsonContent = String.Format(JsonContentFormat, scopesBuilder, targetUri, Environment.MachineName, DateTime.Now);
  98. using (StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType))
  99. using (HttpResponseMessage response = await httpClient.PostAsync(_authorityUrl, content))
  100. {
  101. Trace.WriteLine(" server responded with " + response.StatusCode);
  102. switch (response.StatusCode)
  103. {
  104. case HttpStatusCode.OK:
  105. case HttpStatusCode.Created:
  106. {
  107. string responseText = await response.Content.ReadAsStringAsync();
  108. Match tokenMatch;
  109. if ((tokenMatch = Regex.Match(responseText, @"\s*""token""\s*:\s*""([^""]+)""\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success
  110. && tokenMatch.Groups.Count > 1)
  111. {
  112. string tokenText = tokenMatch.Groups[1].Value;
  113. token = new Token(tokenText, TokenType.Personal);
  114. }
  115. if (token == null)
  116. {
  117. Trace.WriteLine(" authentication failure");
  118. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
  119. }
  120. else
  121. {
  122. Trace.WriteLine(" authentication success: new personal acces token created.");
  123. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Success, token);
  124. }
  125. }
  126. case HttpStatusCode.Unauthorized:
  127. {
  128. if (String.IsNullOrWhiteSpace(authenticationCode)
  129. && response.Headers.Any(x => String.Equals(GitHubOptHeader, x.Key, StringComparison.OrdinalIgnoreCase)))
  130. {
  131. var mfakvp = response.Headers.First(x => String.Equals(GitHubOptHeader, x.Key, StringComparison.OrdinalIgnoreCase) && x.Value != null && x.Value.Count() > 0);
  132. if (mfakvp.Value.First().Contains("app"))
  133. {
  134. Trace.WriteLine(" two-factor app authentication code required");
  135. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorApp);
  136. }
  137. else
  138. {
  139. Trace.WriteLine(" two-factor sms authentication code required");
  140. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorSms);
  141. }
  142. }
  143. else
  144. {
  145. Trace.WriteLine(" authentication failed");
  146. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
  147. }
  148. }
  149. default:
  150. Trace.WriteLine(" authentication failed");
  151. return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
  152. }
  153. }
  154. }
  155. }
  156. public async Task<bool> ValidateCredentials(TargetUri targetUri, Credential credentials)
  157. {
  158. const string ValidationUrl = "https://api.github.com/user/subscriptions";
  159. BaseSecureStore.ValidateTargetUri(targetUri);
  160. BaseSecureStore.ValidateCredential(credentials);
  161. Trace.WriteLine(" GitHubAuthority::ValidateCredentials");
  162. string authString = String.Format("{0}:{1}", credentials.Username, credentials.Password);
  163. byte[] authBytes = Encoding.UTF8.GetBytes(authString);
  164. string authEncode = Convert.ToBase64String(authBytes);
  165. // craft the request header for the GitHub v3 API w/ credentials
  166. using (HttpClientHandler handler = targetUri.HttpClientHandler)
  167. using (HttpClient httpClient = new HttpClient(handler)
  168. {
  169. Timeout = TimeSpan.FromMilliseconds(RequestTimeout)
  170. })
  171. {
  172. httpClient.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent);
  173. httpClient.DefaultRequestHeaders.Add("Accept", GitHubApiAcceptsHeaderValue);
  174. httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + authEncode);
  175. using (HttpResponseMessage response = await httpClient.GetAsync(ValidationUrl))
  176. {
  177. if (response.IsSuccessStatusCode)
  178. {
  179. Trace.WriteLine(" credential validation succeeded");
  180. return true;
  181. }
  182. else
  183. {
  184. Trace.WriteLine(" credential validation failed");
  185. return false;
  186. }
  187. }
  188. }
  189. }
  190. }
  191. }