2013-11-03 23:53:49 +13:00
|
|
|
|
#region License Information (GPL v3)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
ShareX - A program that allows you to take screenshots and share any file type
|
2018-01-02 03:59:14 +13:00
|
|
|
|
Copyright (c) 2007-2018 ShareX Team
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
|
|
Optionally you can also view the license at <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#endregion License Information (GPL v3)
|
|
|
|
|
|
2015-09-17 20:37:58 +12:00
|
|
|
|
// Credits: https://github.com/gpailler
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
|
|
|
|
using Newtonsoft.Json;
|
2016-06-28 05:20:55 +12:00
|
|
|
|
using ShareX.UploadersLib.Properties;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Collections.Specialized;
|
2016-06-28 05:20:55 +12:00
|
|
|
|
using System.Drawing;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
2014-12-11 09:25:20 +13:00
|
|
|
|
namespace ShareX.UploadersLib.FileUploaders
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2016-03-23 06:24:41 +13:00
|
|
|
|
public class JiraFileUploaderService : FileUploaderService
|
|
|
|
|
{
|
|
|
|
|
public override FileDestination EnumValue { get; } = FileDestination.Jira;
|
|
|
|
|
|
2016-06-28 05:20:55 +12:00
|
|
|
|
public override Image ServiceImage => Resources.jira;
|
|
|
|
|
|
2016-03-24 07:47:59 +13:00
|
|
|
|
public override bool CheckConfig(UploadersConfig config)
|
2016-03-23 06:24:41 +13:00
|
|
|
|
{
|
2016-03-24 07:47:59 +13:00
|
|
|
|
return OAuthInfo.CheckOAuth(config.JiraOAuthInfo);
|
2016-03-23 06:24:41 +13:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-04 21:53:11 +12:00
|
|
|
|
public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo)
|
2016-03-23 06:24:41 +13:00
|
|
|
|
{
|
2016-03-24 07:47:59 +13:00
|
|
|
|
return new Jira(config.JiraHost, config.JiraOAuthInfo, config.JiraIssuePrefix);
|
2016-03-23 06:24:41 +13:00
|
|
|
|
}
|
2016-05-01 20:09:11 +12:00
|
|
|
|
|
|
|
|
|
public override TabPage GetUploadersConfigTabPage(UploadersConfigForm form) => form.tpJira;
|
2016-03-23 06:24:41 +13:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-03 23:53:49 +13:00
|
|
|
|
public class Jira : FileUploader, IOAuth
|
|
|
|
|
{
|
|
|
|
|
private const string PathRequestToken = "/plugins/servlet/oauth/request-token";
|
|
|
|
|
private const string PathAuthorize = "/plugins/servlet/oauth/authorize";
|
|
|
|
|
private const string PathAccessToken = "/plugins/servlet/oauth/access-token";
|
|
|
|
|
private const string PathApi = "/rest/api/2";
|
|
|
|
|
private const string PathSearch = PathApi + "/search";
|
|
|
|
|
private const string PathBrowseIssue = "/browse/{0}";
|
|
|
|
|
private const string PathIssueAttachments = PathApi + "/issue/{0}/attachments";
|
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
private static readonly X509Certificate2 jiraCertificate;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
public OAuthInfo AuthInfo { get; set; }
|
|
|
|
|
|
|
|
|
|
private readonly string jiraBaseAddress;
|
|
|
|
|
private readonly string jiraIssuePrefix;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
private Uri jiraRequestToken;
|
|
|
|
|
private Uri jiraAuthorize;
|
|
|
|
|
private Uri jiraAccessToken;
|
|
|
|
|
private Uri jiraPathSearch;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
|
|
|
|
#region Keypair
|
|
|
|
|
|
|
|
|
|
static Jira()
|
|
|
|
|
{
|
|
|
|
|
// Certificate generated using commands:
|
|
|
|
|
// 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
|
|
|
|
|
// pvk2pfx -pvk jira_sharex.pvk -spc jira_sharex.cer -pfx jira_sharex.pfx
|
|
|
|
|
// (Based on: http://nick-howard.blogspot.fr/2011/05/makecert-x509-certificates-and-rsa.html)
|
2014-12-11 12:06:41 +13:00
|
|
|
|
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ShareX.UploadersLib.APIKeys.jira_sharex.pfx"))
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
|
|
|
|
byte[] pfx = new byte[stream.Length];
|
|
|
|
|
stream.Read(pfx, 0, pfx.Length);
|
2018-10-07 08:09:15 +13:00
|
|
|
|
jiraCertificate = new X509Certificate2(pfx, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static string PrivateKey
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2018-10-07 08:09:15 +13:00
|
|
|
|
return jiraCertificate.PrivateKey.ToXmlString(true);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static string PublicKey
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
const int LineBreakIdx = 50;
|
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
string publicKey = Convert.ToBase64String(ExportPublicKey(jiraCertificate.PublicKey));
|
2013-11-03 23:53:49 +13:00
|
|
|
|
int idx = 0;
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
foreach (char c in publicKey)
|
|
|
|
|
{
|
|
|
|
|
sb.Append(c);
|
|
|
|
|
if ((++idx % LineBreakIdx) == 0)
|
|
|
|
|
{
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Join(Environment.NewLine, new[]
|
|
|
|
|
{
|
|
|
|
|
"-----BEGIN PUBLIC KEY-----",
|
|
|
|
|
sb.ToString(),
|
|
|
|
|
"-----END PUBLIC KEY-----"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] ExportPublicKey(PublicKey key)
|
|
|
|
|
{
|
|
|
|
|
// From: http://pstaev.blogspot.fr/2010/08/convert-rsa-public-key-from-xml-to-pem.html
|
|
|
|
|
|
|
|
|
|
byte[] oid = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 }; // Object ID for RSA
|
|
|
|
|
|
|
|
|
|
//Transform the public key to PEM Base64 Format
|
|
|
|
|
List<byte> binaryPublicKey = key.EncodedKeyValue.RawData.ToList();
|
|
|
|
|
binaryPublicKey.Insert(0, 0x0); // Add NULL value
|
|
|
|
|
|
|
|
|
|
CalculateAndAppendLength(ref binaryPublicKey);
|
|
|
|
|
|
|
|
|
|
binaryPublicKey.Insert(0, 0x3);
|
|
|
|
|
binaryPublicKey.InsertRange(0, oid);
|
|
|
|
|
|
|
|
|
|
CalculateAndAppendLength(ref binaryPublicKey);
|
|
|
|
|
|
|
|
|
|
binaryPublicKey.Insert(0, 0x30);
|
|
|
|
|
return binaryPublicKey.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void CalculateAndAppendLength(ref List<byte> binaryData)
|
|
|
|
|
{
|
|
|
|
|
int len = binaryData.Count;
|
|
|
|
|
if (len <= byte.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
binaryData.Insert(0, Convert.ToByte(len));
|
|
|
|
|
binaryData.Insert(0, 0x81); //This byte means that the length fits in one byte
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
binaryData.Insert(0, Convert.ToByte(len % (byte.MaxValue + 1)));
|
|
|
|
|
binaryData.Insert(0, Convert.ToByte(len / (byte.MaxValue + 1)));
|
|
|
|
|
binaryData.Insert(0, 0x82); //This byte means that the length fits in two byte
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Keypair
|
|
|
|
|
|
2013-11-09 06:37:32 +13:00
|
|
|
|
public Jira(string jiraBaseAddress, OAuthInfo oauth, string jiraIssuePrefix = null)
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2018-10-07 08:09:15 +13:00
|
|
|
|
this.jiraBaseAddress = jiraBaseAddress;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
AuthInfo = oauth;
|
2018-10-07 08:09:15 +13:00
|
|
|
|
this.jiraIssuePrefix = jiraIssuePrefix;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
|
|
|
|
InitUris();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetAuthorizationURL()
|
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
using (new SSLBypassHelper())
|
|
|
|
|
{
|
|
|
|
|
Dictionary<string, string> args = new Dictionary<string, string>();
|
|
|
|
|
args[OAuthManager.ParameterCallback] = "oob"; // Request activation code to validate authentication
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
string url = OAuthManager.GenerateQuery(jiraRequestToken.ToString(), args, HttpMethod.POST, AuthInfo);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2014-04-17 05:55:07 +12:00
|
|
|
|
string response = SendRequest(HttpMethod.POST, url);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
if (!string.IsNullOrEmpty(response))
|
|
|
|
|
{
|
2018-10-07 08:09:15 +13:00
|
|
|
|
return OAuthManager.GetAuthorizationURL(response, AuthInfo, jiraAuthorize.ToString());
|
2013-11-16 12:01:49 +13:00
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool GetAccessToken(string verificationCode)
|
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
using (new SSLBypassHelper())
|
|
|
|
|
{
|
|
|
|
|
AuthInfo.AuthVerifier = verificationCode;
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
NameValueCollection nv = GetAccessTokenEx(jiraAccessToken.ToString(), AuthInfo, HttpMethod.POST);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
return nv != null;
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override UploadResult Upload(Stream stream, string fileName)
|
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
using (new SSLBypassHelper())
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2018-10-07 08:09:15 +13:00
|
|
|
|
using (JiraUpload up = new JiraUpload(jiraIssuePrefix, GetSummary))
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
if (up.ShowDialog() == DialogResult.Cancel)
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
return new UploadResult
|
|
|
|
|
{
|
|
|
|
|
IsSuccess = true,
|
|
|
|
|
IsURLExpected = false
|
|
|
|
|
};
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2018-10-07 08:09:15 +13:00
|
|
|
|
Uri uri = Combine(jiraBaseAddress, string.Format(PathIssueAttachments, up.IssueId));
|
2014-04-17 05:55:07 +12:00
|
|
|
|
string query = OAuthManager.GenerateQuery(uri.ToString(), null, HttpMethod.POST, AuthInfo);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
NameValueCollection headers = new NameValueCollection();
|
|
|
|
|
headers.Set("X-Atlassian-Token", "nocheck");
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2016-12-27 05:47:46 +13:00
|
|
|
|
UploadResult res = SendRequestFile(query, stream, fileName, headers: headers);
|
2013-11-16 12:01:49 +13:00
|
|
|
|
if (res.Response.Contains("errorMessages"))
|
|
|
|
|
{
|
2016-04-04 21:13:10 +12:00
|
|
|
|
Errors.Add(res.Response);
|
2013-11-16 12:01:49 +13:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
res.IsURLExpected = true;
|
|
|
|
|
var anonType = new[] { new { thumbnail = "" } };
|
|
|
|
|
var anonObject = JsonConvert.DeserializeAnonymousType(res.Response, anonType);
|
|
|
|
|
res.ThumbnailURL = anonObject[0].thumbnail;
|
2018-10-07 08:09:15 +13:00
|
|
|
|
res.URL = Combine(jiraBaseAddress, string.Format(PathBrowseIssue, up.IssueId)).ToString();
|
2013-11-16 12:01:49 +13:00
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
return res;
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetSummary(string issueId)
|
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
using (new SSLBypassHelper())
|
2013-11-03 23:53:49 +13:00
|
|
|
|
{
|
2013-11-16 12:01:49 +13:00
|
|
|
|
Dictionary<string, string> args = new Dictionary<string, string>();
|
|
|
|
|
args["jql"] = string.Format("issueKey='{0}'", issueId);
|
|
|
|
|
args["maxResults"] = "10";
|
|
|
|
|
args["fields"] = "summary";
|
2018-10-07 08:09:15 +13:00
|
|
|
|
string query = OAuthManager.GenerateQuery(jiraPathSearch.ToString(), args, HttpMethod.GET, AuthInfo);
|
2013-11-16 12:01:49 +13:00
|
|
|
|
|
2014-04-17 05:55:07 +12:00
|
|
|
|
string response = SendRequest(HttpMethod.GET, query);
|
2013-11-16 12:01:49 +13:00
|
|
|
|
if (!string.IsNullOrEmpty(response))
|
|
|
|
|
{
|
|
|
|
|
var anonType = new { issues = new[] { new { key = "", fields = new { summary = "" } } } };
|
|
|
|
|
var res = JsonConvert.DeserializeAnonymousType(response, anonType);
|
|
|
|
|
return res.issues[0].fields.summary;
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
// This query can returns error so we have to remove last error from errors list
|
|
|
|
|
Errors.RemoveAt(Errors.Count - 1);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
|
2013-11-16 12:01:49 +13:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InitUris()
|
|
|
|
|
{
|
2018-10-07 08:09:15 +13:00
|
|
|
|
jiraRequestToken = Combine(jiraBaseAddress, PathRequestToken);
|
|
|
|
|
jiraAuthorize = Combine(jiraBaseAddress, PathAuthorize);
|
|
|
|
|
jiraAccessToken = Combine(jiraBaseAddress, PathAccessToken);
|
|
|
|
|
jiraPathSearch = Combine(jiraBaseAddress, PathSearch);
|
2013-11-09 06:37:32 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Uri Combine(string path1, string path2)
|
|
|
|
|
{
|
|
|
|
|
return new Uri(path1.TrimEnd('/') + path2);
|
2013-11-03 23:53:49 +13:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|