PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/ShareX.UploadersLib/FileUploaders/Jira.cs

https://gitlab.com/billyprice1/ShareX
C# | 293 lines | 215 code | 50 blank | 28 comment | 9 complexity | 849b4514a1af816e0ad09f12e1ca2d7d MD5 | raw file
  1. #region License Information (GPL v3)
  2. /*
  3. ShareX - A program that allows you to take screenshots and share any file type
  4. Copyright (c) 2007-2016 ShareX Team
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. Optionally you can also view the license at <http://www.gnu.org/licenses/>.
  17. */
  18. #endregion License Information (GPL v3)
  19. // Credits: https://github.com/gpailler
  20. using Newtonsoft.Json;
  21. using ShareX.UploadersLib.Properties;
  22. using System;
  23. using System.Collections.Generic;
  24. using System.Collections.Specialized;
  25. using System.Drawing;
  26. using System.IO;
  27. using System.Linq;
  28. using System.Reflection;
  29. using System.Security.Cryptography.X509Certificates;
  30. using System.Text;
  31. using System.Windows.Forms;
  32. namespace ShareX.UploadersLib.FileUploaders
  33. {
  34. public class JiraFileUploaderService : FileUploaderService
  35. {
  36. public override FileDestination EnumValue { get; } = FileDestination.Jira;
  37. public override Image ServiceImage => Resources.jira;
  38. public override bool CheckConfig(UploadersConfig config)
  39. {
  40. return OAuthInfo.CheckOAuth(config.JiraOAuthInfo);
  41. }
  42. public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo)
  43. {
  44. return new Jira(config.JiraHost, config.JiraOAuthInfo, config.JiraIssuePrefix);
  45. }
  46. public override TabPage GetUploadersConfigTabPage(UploadersConfigForm form) => form.tpJira;
  47. }
  48. public class Jira : FileUploader, IOAuth
  49. {
  50. private const string PathRequestToken = "/plugins/servlet/oauth/request-token";
  51. private const string PathAuthorize = "/plugins/servlet/oauth/authorize";
  52. private const string PathAccessToken = "/plugins/servlet/oauth/access-token";
  53. private const string PathApi = "/rest/api/2";
  54. private const string PathSearch = PathApi + "/search";
  55. private const string PathBrowseIssue = "/browse/{0}";
  56. private const string PathIssueAttachments = PathApi + "/issue/{0}/attachments";
  57. private static readonly X509Certificate2 _jiraCertificate;
  58. private readonly string _jiraBaseAddress;
  59. private readonly string _jiraIssuePrefix;
  60. private Uri _jiraRequestToken;
  61. private Uri _jiraAuthorize;
  62. private Uri _jiraAccessToken;
  63. private Uri _jiraPathSearch;
  64. #region Keypair
  65. static Jira()
  66. {
  67. // Certificate generated using commands:
  68. // makecert -pe -n "CN=ShareX" -a sha1 -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -len 1024 -sv jira_sharex.pvk jira_sharex.cer
  69. // pvk2pfx -pvk jira_sharex.pvk -spc jira_sharex.cer -pfx jira_sharex.pfx
  70. // (Based on: http://nick-howard.blogspot.fr/2011/05/makecert-x509-certificates-and-rsa.html)
  71. using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ShareX.UploadersLib.APIKeys.jira_sharex.pfx"))
  72. {
  73. byte[] pfx = new byte[stream.Length];
  74. stream.Read(pfx, 0, pfx.Length);
  75. _jiraCertificate = new X509Certificate2(pfx, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
  76. }
  77. }
  78. internal static string PrivateKey
  79. {
  80. get
  81. {
  82. return _jiraCertificate.PrivateKey.ToXmlString(true);
  83. }
  84. }
  85. internal static string PublicKey
  86. {
  87. get
  88. {
  89. const int LineBreakIdx = 50;
  90. string publicKey = Convert.ToBase64String(ExportPublicKey(_jiraCertificate.PublicKey));
  91. int idx = 0;
  92. StringBuilder sb = new StringBuilder();
  93. foreach (char c in publicKey)
  94. {
  95. sb.Append(c);
  96. if ((++idx % LineBreakIdx) == 0)
  97. {
  98. sb.AppendLine();
  99. }
  100. }
  101. return string.Join(Environment.NewLine, new[]
  102. {
  103. "-----BEGIN PUBLIC KEY-----",
  104. sb.ToString(),
  105. "-----END PUBLIC KEY-----"
  106. });
  107. }
  108. }
  109. private static byte[] ExportPublicKey(PublicKey key)
  110. {
  111. // From: http://pstaev.blogspot.fr/2010/08/convert-rsa-public-key-from-xml-to-pem.html
  112. byte[] oid = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 }; // Object ID for RSA
  113. //Transform the public key to PEM Base64 Format
  114. List<byte> binaryPublicKey = key.EncodedKeyValue.RawData.ToList();
  115. binaryPublicKey.Insert(0, 0x0); // Add NULL value
  116. CalculateAndAppendLength(ref binaryPublicKey);
  117. binaryPublicKey.Insert(0, 0x3);
  118. binaryPublicKey.InsertRange(0, oid);
  119. CalculateAndAppendLength(ref binaryPublicKey);
  120. binaryPublicKey.Insert(0, 0x30);
  121. return binaryPublicKey.ToArray();
  122. }
  123. private static void CalculateAndAppendLength(ref List<byte> binaryData)
  124. {
  125. int len = binaryData.Count;
  126. if (len <= byte.MaxValue)
  127. {
  128. binaryData.Insert(0, Convert.ToByte(len));
  129. binaryData.Insert(0, 0x81); //This byte means that the length fits in one byte
  130. }
  131. else
  132. {
  133. binaryData.Insert(0, Convert.ToByte(len % (byte.MaxValue + 1)));
  134. binaryData.Insert(0, Convert.ToByte(len / (byte.MaxValue + 1)));
  135. binaryData.Insert(0, 0x82); //This byte means that the length fits in two byte
  136. }
  137. }
  138. #endregion Keypair
  139. public Jira(string jiraBaseAddress, OAuthInfo oauth, string jiraIssuePrefix = null)
  140. {
  141. _jiraBaseAddress = jiraBaseAddress;
  142. AuthInfo = oauth;
  143. _jiraIssuePrefix = jiraIssuePrefix;
  144. InitUris();
  145. }
  146. public OAuthInfo AuthInfo { get; set; }
  147. public string GetAuthorizationURL()
  148. {
  149. using (new SSLBypassHelper())
  150. {
  151. Dictionary<string, string> args = new Dictionary<string, string>();
  152. args[OAuthManager.ParameterCallback] = "oob"; // Request activation code to validate authentication
  153. string url = OAuthManager.GenerateQuery(_jiraRequestToken.ToString(), args, HttpMethod.POST, AuthInfo);
  154. string response = SendRequest(HttpMethod.POST, url);
  155. if (!string.IsNullOrEmpty(response))
  156. {
  157. return OAuthManager.GetAuthorizationURL(response, AuthInfo, _jiraAuthorize.ToString());
  158. }
  159. return null;
  160. }
  161. }
  162. public bool GetAccessToken(string verificationCode)
  163. {
  164. using (new SSLBypassHelper())
  165. {
  166. AuthInfo.AuthVerifier = verificationCode;
  167. NameValueCollection nv = GetAccessTokenEx(_jiraAccessToken.ToString(), AuthInfo, HttpMethod.POST);
  168. return nv != null;
  169. }
  170. }
  171. public override UploadResult Upload(Stream stream, string fileName)
  172. {
  173. using (new SSLBypassHelper())
  174. {
  175. using (JiraUpload up = new JiraUpload(_jiraIssuePrefix, GetSummary))
  176. {
  177. if (up.ShowDialog() == DialogResult.Cancel)
  178. {
  179. return new UploadResult
  180. {
  181. IsSuccess = true,
  182. IsURLExpected = false
  183. };
  184. }
  185. Uri uri = Combine(_jiraBaseAddress, string.Format(PathIssueAttachments, up.IssueId));
  186. string query = OAuthManager.GenerateQuery(uri.ToString(), null, HttpMethod.POST, AuthInfo);
  187. NameValueCollection headers = new NameValueCollection();
  188. headers.Set("X-Atlassian-Token", "nocheck");
  189. UploadResult res = UploadData(stream, query, fileName, headers: headers);
  190. if (res.Response.Contains("errorMessages"))
  191. {
  192. Errors.Add(res.Response);
  193. }
  194. else
  195. {
  196. res.IsURLExpected = true;
  197. var anonType = new[] { new { thumbnail = "" } };
  198. var anonObject = JsonConvert.DeserializeAnonymousType(res.Response, anonType);
  199. res.ThumbnailURL = anonObject[0].thumbnail;
  200. res.URL = Combine(_jiraBaseAddress, string.Format(PathBrowseIssue, up.IssueId)).ToString();
  201. }
  202. return res;
  203. }
  204. }
  205. }
  206. private string GetSummary(string issueId)
  207. {
  208. using (new SSLBypassHelper())
  209. {
  210. Dictionary<string, string> args = new Dictionary<string, string>();
  211. args["jql"] = string.Format("issueKey='{0}'", issueId);
  212. args["maxResults"] = "10";
  213. args["fields"] = "summary";
  214. string query = OAuthManager.GenerateQuery(_jiraPathSearch.ToString(), args, HttpMethod.GET, AuthInfo);
  215. string response = SendRequest(HttpMethod.GET, query);
  216. if (!string.IsNullOrEmpty(response))
  217. {
  218. var anonType = new { issues = new[] { new { key = "", fields = new { summary = "" } } } };
  219. var res = JsonConvert.DeserializeAnonymousType(response, anonType);
  220. return res.issues[0].fields.summary;
  221. }
  222. // This query can returns error so we have to remove last error from errors list
  223. Errors.RemoveAt(Errors.Count - 1);
  224. return null;
  225. }
  226. }
  227. private void InitUris()
  228. {
  229. _jiraRequestToken = Combine(_jiraBaseAddress, PathRequestToken);
  230. _jiraAuthorize = Combine(_jiraBaseAddress, PathAuthorize);
  231. _jiraAccessToken = Combine(_jiraBaseAddress, PathAccessToken);
  232. _jiraPathSearch = Combine(_jiraBaseAddress, PathSearch);
  233. }
  234. private Uri Combine(string path1, string path2)
  235. {
  236. return new Uri(path1.TrimEnd('/') + path2);
  237. }
  238. }
  239. }