Reused new OAuth 2 code for the Box plug-in, this was possible by adding the embedded browser. Also refactored code to be more readable, and have more reuse. Fixed problems with Picasa upload and pressing cancel on the PleaseWaitForm. [skip ci]

This commit is contained in:
Robin 2015-04-17 15:44:27 +02:00
parent 9d7299e5ea
commit 1f80d56b10
16 changed files with 408 additions and 277 deletions

View file

@ -31,7 +31,7 @@ namespace Greenshot.Forms {
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
BringToFront = true;
ToFront = true;
}
public BugReportForm(string bugText) : this() {

View file

@ -177,7 +177,7 @@ namespace Greenshot.Forms {
ResumeLayout();
// Fix missing focus
BringToFront = true;
ToFront = true;
TopMost = true;
}

View file

@ -1,10 +1,22 @@
/*
* Created by SharpDevelop.
* User: jens
* Date: 09.04.2012
* Time: 19:24
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
* For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/
*
* 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 1 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, see <http://www.gnu.org/licenses/>.
*/
using GreenshotPlugin.Core;
@ -28,7 +40,7 @@ namespace Greenshot.Help
}
public static void LoadHelp() {
string uri = findOnlineHelpUrl(Language.CurrentLanguage);
string uri = FindOnlineHelpUrl(Language.CurrentLanguage);
if(uri == null) {
uri = Language.HelpFilePath;
}
@ -36,7 +48,7 @@ namespace Greenshot.Help
}
/// <returns>URL of help file in selected ietf, or (if not present) default ietf, or null (if not present, too. probably indicating that there is no internet connection)</returns>
private static string findOnlineHelpUrl(string currentIETF) {
private static string FindOnlineHelpUrl(string currentIETF) {
string ret = null;
string extHelpUrlForCurrrentIETF = EXT_HELP_URL;
@ -45,12 +57,12 @@ namespace Greenshot.Help
extHelpUrlForCurrrentIETF += currentIETF.ToLower() + "/";
}
HttpStatusCode? httpStatusCode = getHttpStatus(extHelpUrlForCurrrentIETF);
HttpStatusCode? httpStatusCode = GetHttpStatus(extHelpUrlForCurrrentIETF);
if(httpStatusCode == HttpStatusCode.OK) {
ret = extHelpUrlForCurrrentIETF;
} else if(httpStatusCode != null && !extHelpUrlForCurrrentIETF.Equals(EXT_HELP_URL)) {
LOG.DebugFormat("Localized online help not found at {0}, will try {1} as fallback", extHelpUrlForCurrrentIETF, EXT_HELP_URL);
httpStatusCode = getHttpStatus(EXT_HELP_URL);
httpStatusCode = GetHttpStatus(EXT_HELP_URL);
if(httpStatusCode == HttpStatusCode.OK) {
ret = EXT_HELP_URL;
} else {
@ -68,9 +80,9 @@ namespace Greenshot.Help
/// </summary>
/// <param name="url">URL for which the HTTP status is to be checked</param>
/// <returns>An HTTP status code, or null if there is none (probably indicating that there is no internet connection available</returns>
private static HttpStatusCode? getHttpStatus(string url) {
private static HttpStatusCode? GetHttpStatus(string url) {
try {
HttpWebRequest req = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
HttpWebRequest req = NetworkHelper.CreateWebRequest(url);
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
return res.StatusCode;
} catch(WebException e) {

View file

@ -18,9 +18,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Windows.Forms;
using Greenshot.IniFile;
using GreenshotPlugin.Core;
using System;
namespace GreenshotBoxPlugin {
/// <summary>
@ -38,10 +40,43 @@ namespace GreenshotBoxPlugin {
public bool AfterUploadLinkToClipBoard;
[IniProperty("UseSharedLink", Description = "Use the shared link, instead of the private, on the clipboard", DefaultValue = "True")]
public bool UseSharedLink;
public bool UseSharedLink {
get;
set;
}
[IniProperty("FolderId", Description = "Folder ID to upload to, only change if you know what you are doing!", DefaultValue = "0")]
public string FolderId {
get;
set;
}
[IniProperty("BoxToken", Description = "Token.", DefaultValue = "")]
public string BoxToken;
[IniProperty("AddFilename", Description = "Is the filename passed on to Box", DefaultValue = "False")]
public bool AddFilename {
get;
set;
}
[IniProperty("RefreshToken", Description = "Box authorization refresh Token", Encrypted = true)]
public string RefreshToken {
get;
set;
}
/// <summary>
/// Not stored
/// </summary>
public string AccessToken {
get;
set;
}
/// <summary>
/// Not stored
/// </summary>
public DateTimeOffset AccessTokenExpires {
get;
set;
}
/// <summary>
/// A form for token

View file

@ -18,16 +18,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Greenshot.IniFile;
using GreenshotPlugin.Core;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Text;
using Greenshot.IniFile;
using GreenshotPlugin.Controls;
using GreenshotPlugin.Core;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace GreenshotBoxPlugin {
@ -37,101 +35,24 @@ namespace GreenshotBoxPlugin {
public static class BoxUtils {
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(BoxUtils));
private static readonly BoxConfiguration Config = IniConfig.GetIniSection<BoxConfiguration>();
private const string RedirectUri = "https://www.box.com/home/";
private const string UploadFileUri = "https://upload.box.com/api/2.0/files/content";
private const string AuthorizeUri = "https://www.box.com/api/oauth2/authorize";
private const string TokenUri = "https://www.box.com/api/oauth2/token";
private const string FilesUri = "https://www.box.com/api/2.0/files/{0}";
private static bool Authorize() {
string authorizeUrl = string.Format("{0}?client_id={1}&response_type=code&state=dropboxplugin&redirect_uri={2}", AuthorizeUri, BoxCredentials.ClientId, RedirectUri);
OAuthLoginForm loginForm = new OAuthLoginForm("Box Authorize", new Size(1060, 600), authorizeUrl, RedirectUri);
loginForm.ShowDialog();
if (!loginForm.isOk) {
return false;
}
var callbackParameters = loginForm.CallbackParameters;
if (callbackParameters == null || !callbackParameters.ContainsKey("code")) {
return false;
}
string authorizationResponse = PostAndReturn(new Uri(TokenUri), string.Format("grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}", callbackParameters["code"], BoxCredentials.ClientId, BoxCredentials.ClientSecret));
var authorization = JsonSerializer.Deserialize<Authorization>(authorizationResponse);
Config.BoxToken = authorization.AccessToken;
IniConfig.Save();
return true;
}
/// <summary>
/// Download a url response as string
/// </summary>
/// <param name="url">An Uri to specify the download location</param>
/// <param name="postMessage"></param>
/// <returns>string with the file content</returns>
public static string PostAndReturn(Uri url, string postMessage) {
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url);
webRequest.Method = "POST";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.ContentType = "application/x-www-form-urlencoded";
byte[] data = Encoding.UTF8.GetBytes(postMessage);
using (var requestStream = webRequest.GetRequestStream()) {
requestStream.Write(data, 0, data.Length);
}
return NetworkHelper.GetResponse(webRequest);
}
/// <summary>
/// Upload parameters by post
/// </summary>
/// <param name="url"></param>
/// <param name="parameters"></param>
/// <returns>response</returns>
public static string HttpPost(string url, IDictionary<string, object> parameters) {
var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
webRequest.Method = "POST";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken);
NetworkHelper.WriteMultipartFormData(webRequest, parameters);
return NetworkHelper.GetResponse(webRequest);
}
/// <summary>
/// Upload file by PUT
/// Put string
/// </summary>
/// <param name="url"></param>
/// <param name="content"></param>
/// <param name="settings">OAuth2Settings</param>
/// <returns>response</returns>
public static string HttpPut(string url, string content) {
var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
webRequest.Method = "PUT";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken);
public static string HttpPut(string url, string content, OAuth2Settings settings) {
var webRequest= OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.PUT, url, settings);
byte[] data = Encoding.UTF8.GetBytes(content);
using (var requestStream = webRequest.GetRequestStream()) {
requestStream.Write(data, 0, data.Length);
}
return NetworkHelper.GetResponse(webRequest);
}
/// <summary>
/// Get REST request
/// </summary>
/// <param name="url"></param>
/// <returns>response</returns>
public static string HttpGet(string url) {
var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
webRequest.Method = "GET";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.Headers.Add("Authorization", "Bearer " + Config.BoxToken);
return NetworkHelper.GetResponse(webRequest);
return NetworkHelper.GetResponseAsString(webRequest);
}
/// <summary>
@ -143,45 +64,53 @@ namespace GreenshotBoxPlugin {
/// <param name="filename">Filename of box upload</param>
/// <returns>url to uploaded image</returns>
public static string UploadToBox(SurfaceContainer image, string title, string filename) {
while (true) {
const string folderId = "0";
if (string.IsNullOrEmpty(Config.BoxToken)) {
if (!Authorize()) {
return null;
}
}
// Fill the OAuth2Settings
OAuth2Settings settings = new OAuth2Settings();
settings.AuthUrlPattern = "https://www.box.com/api/oauth2/authorize?client_id={ClientId}&response_type={response_type}&state{State}&redirect_uri={RedirectUrl}";
settings.TokenUrlPattern = "https://www.box.com/api/oauth2/token";
settings.CloudServiceName = "Box";
settings.ClientId = BoxCredentials.ClientId;
settings.ClientSecret = BoxCredentials.ClientSecret;
settings.RedirectUrl = "https://www.box.com/home/";
settings.BrowserSize = new Size(1060, 600);
settings.AuthorizeMode = OAuth2AuthorizeMode.EmbeddedBrowser;
// Copy the settings from the config, which is kept in memory and on the disk
settings.RefreshToken = Config.RefreshToken;
settings.AccessToken = Config.AccessToken;
settings.AccessTokenExpires = Config.AccessTokenExpires;
try {
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, FilesUri, settings);
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("filename", image);
parameters.Add("parent_id", folderId);
var response = "";
try {
response = HttpPost(UploadFileUri, parameters);
} catch (WebException ex) {
if (ex.Status == WebExceptionStatus.ProtocolError) {
Config.BoxToken = null;
continue;
}
if (Config.AddFilename) {
parameters.Add("filename", image);
}
parameters.Add("parent_id", Config.FolderId);
NetworkHelper.WriteMultipartFormData(webRequest, parameters);
var response = NetworkHelper.GetResponseAsString(webRequest);
LOG.DebugFormat("Box response: {0}", response);
// Check if the token is wrong
if ("wrong auth token".Equals(response)) {
Config.BoxToken = null;
IniConfig.Save();
continue;
}
var upload = JsonSerializer.Deserialize<Upload>(response);
if (upload == null || upload.Entries == null || upload.Entries.Count == 0) return null;
if (Config.UseSharedLink) {
string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}");
string filesResponse = HttpPut(string.Format(FilesUri, upload.Entries[0].Id), "{\"shared_link\": {\"access\": \"open\"}}", settings);
var file = JsonSerializer.Deserialize<FileEntry>(filesResponse);
return file.SharedLink.Url;
}
return string.Format("http://www.box.com/files/0/f/0/1/f_{0}", upload.Entries[0].Id);
} finally {
// Copy the settings back to the config, so they are stored.
Config.RefreshToken = settings.RefreshToken;
Config.AccessToken = settings.AccessToken;
Config.AccessTokenExpires = settings.AccessTokenExpires;
Config.IsDirty = true;
}
}
}

View file

@ -122,8 +122,7 @@ namespace GreenshotImgurPlugin {
if (config.AnonymousAccess) {
// add key, we only use the other parameters for the AnonymousAccess
otherParameters.Add("key", IMGUR_ANONYMOUS_API_KEY);
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters));
webRequest.Method = "POST";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(otherParameters), HTTPMethod.POST);
webRequest.ContentType = "image/" + outputSettings.Format.ToString();
webRequest.ServicePoint.Expect100Continue = false;
@ -194,8 +193,7 @@ namespace GreenshotImgurPlugin {
return;
}
LOG.InfoFormat("Retrieving Imgur image for {0} with url {1}", imgurInfo.Hash, imgurInfo.SmallSquare);
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(imgurInfo.SmallSquare);
webRequest.Method = "GET";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(imgurInfo.SmallSquare, HTTPMethod.GET);
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest);
using (WebResponse response = webRequest.GetResponse()) {
@ -215,8 +213,7 @@ namespace GreenshotImgurPlugin {
public static ImgurInfo RetrieveImgurInfo(string hash, string deleteHash) {
string url = config.ImgurApiUrl + "/image/" + hash;
LOG.InfoFormat("Retrieving Imgur info for {0} with url {1}", hash, url);
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
webRequest.Method = "GET";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET);
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest);
string responseString;
@ -250,10 +247,7 @@ namespace GreenshotImgurPlugin {
try {
string url = config.ImgurApiUrl + "/delete/" + imgurInfo.DeleteHash;
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
//webRequest.Method = "DELETE";
webRequest.Method = "GET";
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(url, HTTPMethod.GET);
webRequest.ServicePoint.Expect100Continue = false;
SetClientId(webRequest);
string responseString;

View file

@ -37,12 +37,6 @@ namespace GreenshotPicasaPlugin {
[IniProperty("AfterUploadLinkToClipBoard", Description = "After upload send Picasa link to clipboard.", DefaultValue = "true")]
public bool AfterUploadLinkToClipBoard;
[IniProperty("RefreshToken", Description = "Picasa refresh Token", Encrypted = true)]
public string RefreshToken {
get;
set;
}
[IniProperty("AddFilename", Description = "Is the filename passed on to Picasa", DefaultValue = "False")]
public bool AddFilename {
get;
@ -61,6 +55,12 @@ namespace GreenshotPicasaPlugin {
set;
}
[IniProperty("RefreshToken", Description = "Picasa authorization refresh Token", Encrypted = true)]
public string RefreshToken {
get;
set;
}
/// <summary>
/// Not stored
/// </summary>

View file

@ -50,10 +50,12 @@ namespace GreenshotPicasaPlugin {
OAuth2Settings settings = new OAuth2Settings();
settings.AuthUrlPattern = AuthUrl;
settings.TokenUrlPattern = TokenUrl;
settings.CloudServiceName = "Picasa";
settings.AdditionalAttributes.Add("response_type", "code");
settings.AdditionalAttributes.Add("scope", PicasaScope);
settings.ClientId = PicasaCredentials.ClientId;
settings.ClientSecret = PicasaCredentials.ClientSecret;
settings.AuthorizeMode = OAuth2AuthorizeMode.LocalServer;
// Copy the settings from the config, which is kept in memory and on the disk
settings.RefreshToken = Config.RefreshToken;
@ -61,27 +63,14 @@ namespace GreenshotPicasaPlugin {
settings.AccessTokenExpires = Config.AccessTokenExpires;
try {
// Get Refresh / Access token
if (string.IsNullOrEmpty(settings.RefreshToken)) {
OAuth2Helper.AuthenticateViaLocalServer(settings);
}
if (settings.IsAccessTokenExpired) {
OAuth2Helper.GenerateAccessToken(settings);
}
var webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum));
webRequest.Method = "POST";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
OAuth2Helper.AddOAuth2Credentials(webRequest, settings);
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, string.Format(UploadUrl, Config.UploadUser, Config.UploadAlbum), settings);
if (Config.AddFilename) {
webRequest.Headers.Add("Slug", NetworkHelper.EscapeDataString(filename));
}
SurfaceContainer container = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
container.Upload(webRequest);
string response = NetworkHelper.GetResponse(webRequest);
string response = NetworkHelper.GetResponseAsString(webRequest);
return ParseResponse(response);
} finally {
@ -89,6 +78,7 @@ namespace GreenshotPicasaPlugin {
Config.RefreshToken = settings.RefreshToken;
Config.AccessToken = settings.AccessToken;
Config.AccessTokenExpires = settings.AccessTokenExpires;
Config.IsDirty = true;
}
}

View file

@ -87,7 +87,7 @@ namespace GreenshotPlugin.Controls {
/// <summary>
/// When this is set, the form will be brought to the foreground as soon as it is shown.
/// </summary>
protected bool BringToFront {
protected bool ToFront {
get;
set;
}
@ -162,7 +162,7 @@ namespace GreenshotPlugin.Controls {
/// <param name="e">EventArgs</param>
protected override void OnShown(EventArgs e) {
base.OnShown(e);
if (BringToFront) {
if (ToFront) {
WindowDetails.ToForeground(Handle);
}
}

View file

@ -44,37 +44,37 @@ namespace GreenshotPlugin.Controls {
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.addressTextBox = new System.Windows.Forms.TextBox();
this.browser = new ExtendedWebBrowser();
this._addressTextBox = new System.Windows.Forms.TextBox();
this._browser = new ExtendedWebBrowser();
this.SuspendLayout();
//
// addressTextBox
// _addressTextBox
//
this.addressTextBox.Cursor = System.Windows.Forms.Cursors.Arrow;
this.addressTextBox.Dock = System.Windows.Forms.DockStyle.Top;
this.addressTextBox.Enabled = false;
this.addressTextBox.Location = new System.Drawing.Point(0, 0);
this.addressTextBox.Name = "addressTextBox";
this.addressTextBox.Size = new System.Drawing.Size(595, 20);
this.addressTextBox.TabIndex = 3;
this.addressTextBox.TabStop = false;
this._addressTextBox.Cursor = System.Windows.Forms.Cursors.Arrow;
this._addressTextBox.Dock = System.Windows.Forms.DockStyle.Top;
this._addressTextBox.Enabled = false;
this._addressTextBox.Location = new System.Drawing.Point(0, 0);
this._addressTextBox.Name = "addressTextBox";
this._addressTextBox.Size = new System.Drawing.Size(595, 20);
this._addressTextBox.TabIndex = 3;
this._addressTextBox.TabStop = false;
//
// browser
// _browser
//
this.browser.Dock = System.Windows.Forms.DockStyle.Fill;
this.browser.Location = new System.Drawing.Point(0, 20);
this.browser.MinimumSize = new System.Drawing.Size(100, 100);
this.browser.Name = "browser";
this.browser.Size = new System.Drawing.Size(595, 295);
this.browser.TabIndex = 4;
this._browser.Dock = System.Windows.Forms.DockStyle.Fill;
this._browser.Location = new System.Drawing.Point(0, 20);
this._browser.MinimumSize = new System.Drawing.Size(100, 100);
this._browser.Name = "browser";
this._browser.Size = new System.Drawing.Size(595, 295);
this._browser.TabIndex = 4;
//
// OAuthLoginForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(595, 315);
this.Controls.Add(this.browser);
this.Controls.Add(this.addressTextBox);
this.Controls.Add(this._browser);
this.Controls.Add(this._addressTextBox);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "OAuthLoginForm";
@ -85,8 +85,8 @@ namespace GreenshotPlugin.Controls {
#endregion
private System.Windows.Forms.TextBox addressTextBox;
private ExtendedWebBrowser browser;
private System.Windows.Forms.TextBox _addressTextBox;
private ExtendedWebBrowser _browser;
}
}

View file

@ -35,31 +35,33 @@ namespace GreenshotPlugin.Controls {
/// </summary>
public partial class OAuthLoginForm : Form {
private static readonly ILog LOG = LogManager.GetLogger(typeof(OAuthLoginForm));
private string callbackUrl = null;
private IDictionary<string, string> callbackParameters = null;
private string _callbackUrl = null;
private IDictionary<string, string> _callbackParameters = null;
public IDictionary<string, string> CallbackParameters {
get { return callbackParameters; }
get {
return _callbackParameters;
}
}
public bool isOk {
public bool IsOk {
get {
return DialogResult == DialogResult.OK;
}
}
public OAuthLoginForm(string browserTitle, Size size, string authorizationLink, string callbackUrl) {
this.callbackUrl = callbackUrl;
_callbackUrl = callbackUrl;
InitializeComponent();
ClientSize = size;
Icon = GreenshotResources.getGreenshotIcon();
Text = browserTitle;
addressTextBox.Text = authorizationLink;
_addressTextBox.Text = authorizationLink;
// The script errors are suppressed by using the ExtendedWebBrowser
browser.ScriptErrorsSuppressed = false;
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
browser.Navigate(new Uri(authorizationLink));
_browser.ScriptErrorsSuppressed = false;
_browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Browser_DocumentCompleted);
_browser.Navigate(new Uri(authorizationLink));
}
/// <summary>
@ -71,33 +73,24 @@ namespace GreenshotPlugin.Controls {
WindowDetails.ToForeground(Handle);
}
private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
LOG.DebugFormat("document completed with url: {0}", browser.Url);
checkUrl();
}
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) {
LOG.DebugFormat("Navigating to url: {0}", browser.Url);
addressTextBox.Text = e.Url.ToString();
private void Browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
LOG.DebugFormat("document completed with url: {0}", _browser.Url);
CheckUrl();
}
private void browser_Navigated(object sender, WebBrowserNavigatedEventArgs e) {
LOG.DebugFormat("Navigated to url: {0}", browser.Url);
checkUrl();
}
private void checkUrl() {
if (browser.Url.ToString().StartsWith(callbackUrl)) {
string queryParams = browser.Url.Query;
private void CheckUrl() {
if (_browser.Url.ToString().StartsWith(_callbackUrl)) {
string queryParams = _browser.Url.Query;
if (queryParams.Length > 0) {
queryParams = NetworkHelper.UrlDecode(queryParams);
//Store the Token and Token Secret
callbackParameters = NetworkHelper.ParseQueryString(queryParams);
_callbackParameters = NetworkHelper.ParseQueryString(queryParams);
}
DialogResult = DialogResult.OK;
}
}
private void addressTextBox_KeyPress(object sender, KeyPressEventArgs e) {
private void AddressTextBox_KeyPress(object sender, KeyPressEventArgs e) {
//Cancel the key press so the user can't enter a new url
e.Handled = true;
}

View file

@ -47,7 +47,7 @@ namespace GreenshotPlugin.Controls {
trackBarJpegQuality.Value = Settings.JPGQuality;
textBoxJpegQuality.Enabled = OutputFormat.jpg.Equals(outputSettings.Format);
textBoxJpegQuality.Text = Settings.JPGQuality.ToString();
BringToFront = true;
ToFront = true;
}
void Button_okClick(object sender, EventArgs e) {

View file

@ -33,6 +33,17 @@ using System.Text;
using System.Text.RegularExpressions;
namespace GreenshotPlugin.Core {
/// <summary>
/// HTTP Method to make sure we have the correct method
/// </summary>
public enum HTTPMethod {
GET,
POST,
PUT,
DELETE,
HEAD
};
/// <summary>
/// Description of NetworkHelper.
/// </summary>
@ -55,10 +66,7 @@ namespace GreenshotPlugin.Core {
/// <returns>string with the file content</returns>
public static string GetAsString(Uri uri) {
HttpWebRequest webRequest = CreateWebRequest(uri);
webRequest.Method = "GET";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
return GetResponse(webRequest);
return GetResponseAsString(CreateWebRequest(uri));
}
/// <summary>
@ -143,7 +151,7 @@ namespace GreenshotPlugin.Core {
}
/// <summary>
/// Helper method to create a web request, eventually with proxy
/// Helper method to create a web request with a lot of default settings
/// </summary>
/// <param name="uri">string with uri to connect to</param>
/// <returns>WebRequest</returns>
@ -151,6 +159,28 @@ namespace GreenshotPlugin.Core {
return CreateWebRequest(new Uri(uri));
}
/// <summary>
/// Helper method to create a web request with a lot of default settings
/// </summary>
/// <param name="uri">string with uri to connect to</param>
/// /// <param name="method">Method to use</param>
/// <returns>WebRequest</returns>
public static HttpWebRequest CreateWebRequest(string uri, HTTPMethod method) {
return CreateWebRequest(new Uri(uri), method);
}
/// <summary>
/// Helper method to create a web request with a lot of default settings
/// </summary>
/// <param name="uri">Uri with uri to connect to</param>
/// <param name="method">Method to use</param>
/// <returns>WebRequest</returns>
public static HttpWebRequest CreateWebRequest(Uri uri, HTTPMethod method) {
HttpWebRequest webRequest = CreateWebRequest(uri);
webRequest.Method = method.ToString();
return webRequest;
}
/// <summary>
/// Helper method to create a web request, eventually with proxy
/// </summary>
@ -164,6 +194,10 @@ namespace GreenshotPlugin.Core {
// BUG-1655: Fix that Greenshot always uses the default proxy even if the "use default proxy" checkbox is unset
webRequest.Proxy = null;
}
// Make sure the default credentials are available
webRequest.Credentials = CredentialCache.DefaultCredentials;
// Allow redirect, this is usually needed so that we don't get a problem when a service moves
webRequest.AllowAutoRedirect = true;
// Set default timeouts
webRequest.Timeout = Config.WebRequestTimeout*1000;
@ -365,7 +399,7 @@ namespace GreenshotPlugin.Core {
using (var streamWriter = new StreamWriter(requestStream, Encoding.UTF8)) {
streamWriter.Write(urlEncoded);
}
return GetResponse(webRequest);
return GetResponseAsString(webRequest);
}
/// <summary>
@ -388,7 +422,7 @@ namespace GreenshotPlugin.Core {
/// <param name="webRequest">The request object.</param>
/// <returns>The response data.</returns>
/// TODO: This method should handle the StatusCode better!
public static string GetResponse(HttpWebRequest webRequest) {
public static string GetResponseAsString(HttpWebRequest webRequest) {
string responseData = null;
try {
HttpWebResponse response = (HttpWebResponse) webRequest.GetResponse();
@ -429,7 +463,7 @@ namespace GreenshotPlugin.Core {
public static DateTime GetLastModified(Uri uri) {
try {
HttpWebRequest webRequest = CreateWebRequest(uri);
webRequest.Method = "HEAD";
webRequest.Method = HTTPMethod.HEAD.ToString();
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
LOG.DebugFormat("RSS feed was updated at {0}", webResponse.LastModified);
return webResponse.LastModified;

View file

@ -46,9 +46,15 @@ namespace GreenshotPlugin.Core {
}
/// <summary>
/// Used HTTP Method, this is for the OAuth 1.x protocol
/// Specify the autorize mode that is used to get the token from the cloud service.
/// </summary>
public enum HTTPMethod { GET, POST, PUT, DELETE };
public enum OAuth2AuthorizeMode {
Unknown, // Will give an exception, caller needs to specify another value
LocalServer, // Will specify a redirect URL to http://localhost:port/authorize, while having a HttpListener
MonitorTitle, // Not implemented yet: Will monitor for title changes
Pin, // Not implemented yet: Will ask the user to enter the shown PIN
EmbeddedBrowser // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect
}
/// <summary>
/// Settings for the OAuth 2 protocol
@ -58,6 +64,28 @@ namespace GreenshotPlugin.Core {
AdditionalAttributes = new Dictionary<string, string>();
// Create a default state
State = Guid.NewGuid().ToString();
AuthorizeMode = OAuth2AuthorizeMode.Unknown;
}
public OAuth2AuthorizeMode AuthorizeMode {
get;
set;
}
/// <summary>
/// Specify the name of the cloud service, so it can be used in window titles, logs etc
/// </summary>
public string CloudServiceName {
get;
set;
}
/// <summary>
/// Specify the size of the embedded Browser, if using this
/// </summary>
public Size BrowserSize {
get;
set;
}
/// <summary>
@ -242,7 +270,7 @@ namespace GreenshotPlugin.Core {
private readonly string _consumerKey;
private readonly string _consumerSecret;
// default browser size
// default _browser size
private Size _browserSize = new Size(864, 587);
private string _loginTitle = "Authorize Greenshot access";
@ -479,7 +507,7 @@ namespace GreenshotPlugin.Core {
LOG.DebugFormat("Opening AuthorizationLink: {0}", AuthorizationLink);
OAuthLoginForm oAuthLoginForm = new OAuthLoginForm(LoginTitle, BrowserSize, AuthorizationLink, CallbackUrl);
oAuthLoginForm.ShowDialog();
if (oAuthLoginForm.isOk) {
if (oAuthLoginForm.IsOk) {
if (oAuthLoginForm.CallbackParameters != null) {
string tokenValue;
if (oAuthLoginForm.CallbackParameters.TryGetValue(OAUTH_TOKEN_KEY, out tokenValue)) {
@ -812,11 +840,9 @@ namespace GreenshotPlugin.Core {
}
}
// Create webrequest
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestURL);
webRequest.Method = method.ToString();
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(requestURL, method);
webRequest.ServicePoint.Expect100Continue = false;
webRequest.UserAgent = _userAgent;
webRequest.Timeout = 100000;
if (UseHTTPHeadersForAuthorization && authHeader != null) {
LOG.DebugFormat("Authorization: OAuth {0}", authHeader);
@ -860,7 +886,7 @@ namespace GreenshotPlugin.Core {
string responseData;
try {
responseData = NetworkHelper.GetResponse(webRequest);
responseData = NetworkHelper.GetResponseAsString(webRequest);
LOG.DebugFormat("Response: {0}", responseData);
} catch (Exception ex) {
LOG.Error("Couldn't retrieve response: ", ex);
@ -879,6 +905,7 @@ namespace GreenshotPlugin.Core {
/// </summary>
public class LocalServerCodeReceiver {
private static readonly ILog LOG = LogManager.GetLogger(typeof(LocalServerCodeReceiver));
private readonly ManualResetEvent _ready = new ManualResetEvent(true);
private string _loopbackCallback = "http://localhost:{0}/authorize/";
/// <summary>
@ -896,9 +923,9 @@ namespace GreenshotPlugin.Core {
private string _closePageResponse =
@"<html>
<head><title>OAuth 2.0 Authentication Token Received</title></head>
<head><title>OAuth 2.0 Authentication CloudServiceName</title></head>
<body>
Greenshot received verification code. You can close this browser / tab if it is not closed itself...
Greenshot received information from CloudServiceName. You can close this browser / tab if it is not closed itself...
<script type='text/javascript'>
window.setTimeout(function() {
window.open('', '_self', '');
@ -912,7 +939,8 @@ Greenshot received verification code. You can close this browser / tab if it is
</html>";
/// <summary>
/// HTML code to to return the browser, default it will try to close the browser / tab, this won't always work.
/// HTML code to to return the _browser, default it will try to close the _browser / tab, this won't always work.
/// You can use CloudServiceName where you want to show the CloudServiceName from your OAuth2 settings
/// </summary>
public string ClosePageResponse {
get {
@ -937,6 +965,11 @@ Greenshot received verification code. You can close this browser / tab if it is
}
}
private string _cloudServiceName;
private IDictionary<string, string> _returnValues = new Dictionary<string, string>();
/// <summary>
/// The OAuth code receiver
/// </summary>
@ -945,6 +978,7 @@ Greenshot received verification code. You can close this browser / tab if it is
public IDictionary<string, string> ReceiveCode(OAuth2Settings oauth2Settings) {
// Set the redirect URL on the settings
oauth2Settings.RedirectUrl = RedirectUri;
_cloudServiceName = oauth2Settings.CloudServiceName;
using (var listener = new HttpListener()) {
listener.Prefixes.Add(oauth2Settings.RedirectUrl);
try {
@ -956,31 +990,67 @@ Greenshot received verification code. You can close this browser / tab if it is
Process.Start(authorizationUrl);
// Wait to get the authorization code response.
var context = listener.GetContext();
try {
NameValueCollection nameValueCollection = context.Request.QueryString;
var context = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
_ready.Reset();
// Write a "close" response.
using (var writer = new StreamWriter(context.Response.OutputStream)) {
writer.WriteLine(ClosePageResponse);
writer.Flush();
}
// Create a new response URL with a dictionary that contains all the response query parameters.
IDictionary<string, string> returnValues = new Dictionary<string, string>();
foreach (var name in nameValueCollection.AllKeys) {
if (!returnValues.ContainsKey(name)) {
returnValues.Add(name, nameValueCollection[name]);
}
}
return returnValues;
} finally {
context.Response.OutputStream.Close();
while (!context.AsyncWaitHandle.WaitOne(1000, true)) {
LOG.Debug("Waiting for response");
}
} catch (Exception) {
// Make sure we can clean up, also if the thead is aborted
_ready.Set();
throw;
} finally {
_ready.WaitOne();
listener.Close();
}
}
return _returnValues;
}
/// <summary>
/// Handle a connection async, this allows us to break the waiting
/// </summary>
/// <param name="result">IAsyncResult</param>
private void ListenerCallback(IAsyncResult result) {
HttpListener listener = (HttpListener)result.AsyncState;
//If not listening return immediately as this method is called one last time after Close()
if (!listener.IsListening) {
return;
}
// Use EndGetContext to complete the asynchronous operation.
HttpListenerContext context = listener.EndGetContext(result);
// Handle request
HttpListenerRequest request = context.Request;
try {
NameValueCollection nameValueCollection = request.QueryString;
// Get response object.
using (HttpListenerResponse response = context.Response) {
// Write a "close" response.
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(ClosePageResponse.Replace("CloudServiceName", _cloudServiceName));
// Write to response stream.
response.ContentLength64 = buffer.Length;
using (var stream = response.OutputStream) {
stream.Write(buffer, 0, buffer.Length);
}
}
// Create a new response URL with a dictionary that contains all the response query parameters.
foreach (var name in nameValueCollection.AllKeys) {
if (!_returnValues.ContainsKey(name)) {
_returnValues.Add(name, nameValueCollection[name]);
}
}
} catch (Exception ex) {
context.Response.OutputStream.Close();
throw;
}
_ready.Set();
}
/// <summary>
@ -1002,23 +1072,6 @@ Greenshot received verification code. You can close this browser / tab if it is
/// Code to simplify OAuth 2
/// </summary>
public static class OAuth2Helper {
/// <summary>
/// Upload parameters by post
/// </summary>
/// <param name="url"></param>
/// <param name="parameters">Form-Url-Parameters</param>
/// <param name="settings">OAuth2Settings</param>
/// <returns>response</returns>
public static string HttpPost(string url, IDictionary<string, object> parameters, OAuth2Settings settings) {
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url);
webRequest.Method = "POST";
webRequest.KeepAlive = true;
webRequest.Credentials = CredentialCache.DefaultCredentials;
AddOAuth2Credentials(webRequest, settings);
return NetworkHelper.UploadFormUrlEncoded(webRequest, parameters);
}
/// <summary>
/// Generate an OAuth 2 Token by using the supplied code
/// </summary>
@ -1033,7 +1086,8 @@ Greenshot received verification code. You can close this browser / tab if it is
data.Add("client_secret", settings.ClientSecret);
data.Add("grant_type", "authorization_code");
string accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings);
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.FormattedTokenUrl, HTTPMethod.POST);
string accessTokenJsonResult = NetworkHelper.UploadFormUrlEncoded(webRequest, data);
IDictionary<string, object> refreshTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult);
// gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp
// "access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
@ -1061,7 +1115,9 @@ Greenshot received verification code. You can close this browser / tab if it is
data.Add("client_secret", settings.ClientSecret);
data.Add("grant_type", "refresh_token");
string accessTokenJsonResult = HttpPost(settings.FormattedTokenUrl, data, settings);
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(settings.FormattedTokenUrl, HTTPMethod.POST);
string accessTokenJsonResult = NetworkHelper.UploadFormUrlEncoded(webRequest, data);
// gives as described here: https://developers.google.com/identity/protocols/OAuth2InstalledApp
// "access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
// "expires_in":3920,
@ -1076,26 +1132,78 @@ Greenshot received verification code. You can close this browser / tab if it is
}
/// <summary>
/// Authenticate via a local server by using the LocalServerCodeReceiver
/// If this works, immediately generate a refresh token afterwards, otherwise this throws an exception
/// Authenticate by using the mode specified in the settings
/// </summary>
/// <param name="settings">OAuth2Settings</param>
/// <returns>false if it was canceled, true if it worked, exception if not</returns>
public static bool Authenticate(OAuth2Settings settings) {
bool completed = true;
switch (settings.AuthorizeMode) {
case OAuth2AuthorizeMode.LocalServer:
completed = AuthenticateViaLocalServer(settings);
break;
case OAuth2AuthorizeMode.EmbeddedBrowser:
completed = AuthenticateViaEmbeddedBrowser(settings);
break;
default:
throw new NotImplementedException(string.Format("Authorize mode '{0}' is not 'yet' implemented.", settings.AuthorizeMode));
}
return completed;
}
/// <summary>
/// Authenticate via an embedded browser
/// If this works, return the code
/// </summary>
/// <param name="settings">OAuth2Settings with the Auth / Token url etc</param>
public static void AuthenticateViaLocalServer(OAuth2Settings settings) {
/// <returns>true if completed, false if canceled</returns>
private static bool AuthenticateViaEmbeddedBrowser(OAuth2Settings settings) {
if (string.IsNullOrEmpty(settings.CloudServiceName)) {
throw new ArgumentNullException("CloudServiceName");
}
if (settings.BrowserSize == Size.Empty) {
throw new ArgumentNullException("BrowserSize");
}
OAuthLoginForm loginForm = new OAuthLoginForm(string.Format("Authorize {0}", settings.CloudServiceName), settings.BrowserSize, settings.FormattedAuthUrl, settings.RedirectUrl);
loginForm.ShowDialog();
if (loginForm.IsOk) {
string code;
if (loginForm.CallbackParameters.TryGetValue("code", out code) && !string.IsNullOrEmpty(code)) {
GenerateRefreshToken(code, settings);
return true;
}
}
return false;
}
/// <summary>
/// Authenticate via a local server by using the LocalServerCodeReceiver
/// If this works, return the code
/// </summary>
/// <param name="settings">OAuth2Settings with the Auth / Token url etc</param>
/// <returns>true if completed</returns>
private static bool AuthenticateViaLocalServer(OAuth2Settings settings) {
var codeReceiver = new LocalServerCodeReceiver();
IDictionary<string, string> result = codeReceiver.ReceiveCode(settings);
string code;
if (result.TryGetValue("code", out code)) {
if (result.TryGetValue("code", out code) && !string.IsNullOrEmpty(code)) {
GenerateRefreshToken(code, settings);
return true;
}
string error;
if (result.TryGetValue("error", out error)) {
string errorDescription;
if (result.TryGetValue("error_description", out errorDescription)) {
throw new Exception(errorDescription);
}
if ("access_denied" == error) {
throw new UnauthorizedAccessException("Access denied");
} else {
throw new Exception(error);
}
}
return false;
}
/// <summary>
@ -1108,5 +1216,38 @@ Greenshot received verification code. You can close this browser / tab if it is
webRequest.Headers.Add("Authorization", "Bearer " + settings.AccessToken);
}
}
/// <summary>
/// Check and authenticate or refresh tokens
/// </summary>
/// <param name="settings">OAuth2Settings</param>
public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) {
// Get Refresh / Access token
if (string.IsNullOrEmpty(settings.RefreshToken)) {
if (!OAuth2Helper.Authenticate(settings)) {
throw new Exception("Authentication cancelled");
}
}
if (settings.IsAccessTokenExpired) {
OAuth2Helper.GenerateAccessToken(settings);
}
}
/// <summary>
/// CreateWebRequest ready for OAuth 2 access
/// </summary>
/// <param name="method">HTTPMethod</param>
/// <param name="url"></param>
/// <param name="settings">OAuth2Settings</param>
/// <returns>HttpWebRequest</returns>
public static HttpWebRequest CreateOAuth2WebRequest(HTTPMethod method, string url, OAuth2Settings settings) {
CheckAndAuthenticateOrRefresh(settings);
HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(url, method);
OAuth2Helper.AddOAuth2Credentials(webRequest, settings);
return webRequest;
}
}
}

View file

@ -18,6 +18,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Globalization;
@ -113,10 +114,9 @@ namespace GreenshotPlugin.Core {
/// </summary>
/// <returns>Dictionary<string, Dictionary<string, RssFile>> with files and their RssFile "description"</returns>
public static Dictionary<string, Dictionary<string, SourceforgeFile>> readRSS() {
HttpWebRequest webRequest;
XmlDocument rssDoc = new XmlDocument();
try {
webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(RSSFEED);
HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(RSSFEED);
XmlTextReader rssReader = new XmlTextReader(webRequest.GetResponse().GetResponseStream());
// Load the XML content into a XmlDocument

View file

@ -58,7 +58,10 @@ namespace Greenshot.IniFile {
/// <summary>
/// Flag to specify if values have been changed
/// </summary>
public bool IsDirty = false;
public bool IsDirty {
get;
set;
}
/// <summary>
/// Supply values we can't put as defaults