Continuing work on the OAuth simplification, and going via our website.

This commit is contained in:
Robin Krom 2020-10-29 16:03:30 +01:00
commit 3d61b8e28c
6 changed files with 137 additions and 51 deletions

View file

@ -1,20 +1,20 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel
* *
* For more information see: http://getgreenshot.org/ * For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or * the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@ -32,7 +32,7 @@ namespace GreenshotDropboxPlugin {
public DropboxDestination(DropboxPlugin plugin) { public DropboxDestination(DropboxPlugin plugin) {
_plugin = plugin; _plugin = plugin;
} }
public override string Designation => "Dropbox"; public override string Designation => "Dropbox";
public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item); public override string Description => Language.GetString("dropbox", LangKey.upload_menu_item);
@ -43,8 +43,8 @@ namespace GreenshotDropboxPlugin {
return (Image)resources.GetObject("Dropbox"); return (Image)resources.GetObject("Dropbox");
} }
} }
public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) { public override ExportInformation ExportCapture(bool manually, ISurface surface, ICaptureDetails captureDetails) {
ExportInformation exportInformation = new ExportInformation(Designation, Description); ExportInformation exportInformation = new ExportInformation(Designation, Description);
bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl); bool uploaded = _plugin.Upload(captureDetails, surface, out var uploadUrl);
if (uploaded) { if (uploaded) {

View file

@ -1,27 +1,26 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom, Francis Noel
* *
* For more information see: http://getgreenshot.org/ * For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or * the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using GreenshotPlugin.Controls; using GreenshotPlugin.Controls;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
@ -102,20 +101,16 @@ namespace GreenshotDropboxPlugin {
public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) { public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) {
uploadUrl = null; uploadUrl = null;
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false); SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false);
try { try
string dropboxUrl = null; {
new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait), bool result = false;
new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait),
delegate delegate
{ {
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails)); result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails);
dropboxUrl = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, filename);
} }
); );
if (dropboxUrl == null) { return result;
return false;
}
uploadUrl = dropboxUrl;
return true;
} catch (Exception e) { } catch (Exception e) {
Log.Error(e); Log.Error(e);
MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message); MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message);

View file

@ -20,11 +20,13 @@
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using GreenshotPlugin.Core; using GreenshotPlugin.Core;
using GreenshotPlugin.Core.OAuth; using GreenshotPlugin.Core.OAuth;
using GreenshotPlugin.IniFile; using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces; using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin; using GreenshotPlugin.Interfaces.Plugin;
using Newtonsoft.Json;
namespace GreenshotDropboxPlugin { namespace GreenshotDropboxPlugin {
/// <summary> /// <summary>
@ -37,11 +39,11 @@ namespace GreenshotDropboxPlugin {
private DropboxUtils() { private DropboxUtils() {
} }
public static string UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, string filename) { public static bool UploadToDropbox(ISurface surfaceToUpload, SurfaceOutputSettings outputSettings, ICaptureDetails captureDetails)
{
var oauth2Settings = new OAuth2Settings var oauth2Settings = new OAuth2Settings
{ {
AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}&token_access_type=offline", AuthUrlPattern = "https://api.dropbox.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}&redirect_uri={RedirectUrl}",
TokenUrl = "https://api.dropbox.com/oauth2/token", TokenUrl = "https://api.dropbox.com/oauth2/token",
RedirectUrl = "https://getgreenshot.org/authorize/dropbox", RedirectUrl = "https://getgreenshot.org/authorize/dropbox",
CloudServiceName = "Dropbox", CloudServiceName = "Dropbox",
@ -55,20 +57,28 @@ namespace GreenshotDropboxPlugin {
try try
{ {
string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails));
SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename); SurfaceContainer image = new SurfaceContainer(surfaceToUpload, outputSettings, filename);
IDictionary<string, object> parameters = new Dictionary<string, object> IDictionary<string, object> arguments = new Dictionary<string, object>
{ {
{ "file", image },
{ "autorename", true }, { "autorename", true },
{ "mute", true}, { "mute", true },
{ "path", filename} { "path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')}
}; };
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://api.dropbox.com//2/files/upload", oauth2Settings); IDictionary<string, object> headers = new Dictionary<string, object>
NetworkHelper.WriteMultipartFormData(webRequest, parameters); {
var response = NetworkHelper.GetResponseAsString(webRequest); { "Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)}
Log.DebugFormat("Upload response: {0}", response); };
} catch (Exception ex) { var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://content.dropboxapi.com/2/files/upload", oauth2Settings);
NetworkHelper.Post(webRequest, headers, image);
var responseString = NetworkHelper.GetResponseAsString(webRequest);
Log.DebugFormat("Upload response: {0}", responseString);
var response = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseString);
return response.ContainsKey("id");
}
catch (Exception ex) {
Log.Error("Upload error: ", ex); Log.Error("Upload error: ", ex);
throw; throw;
} finally { } finally {
@ -78,7 +88,6 @@ namespace GreenshotDropboxPlugin {
DropboxConfig.IsDirty = true; DropboxConfig.IsDirty = true;
IniConfig.Save(); IniConfig.Save();
} }
return null; }
}
} }
} }

View file

@ -358,6 +358,84 @@ namespace GreenshotPlugin.Core {
WriteMultipartFormData(formDataStream, boundary, postParameters); WriteMultipartFormData(formDataStream, boundary, postParameters);
} }
/// <summary>
/// Post content HttpWebRequest
/// </summary>
/// <param name="webRequest">HttpWebRequest to write the multipart form data to</param>
/// <param name="headers">IDictionary with the headers</param>
/// <param name="binaryContainer">IBinaryContainer</param>
public static void Post(HttpWebRequest webRequest, IDictionary<string, object> headers, IBinaryContainer binaryContainer = null)
{
foreach (var header in headers)
{
switch (header.Key)
{
case "Content-Type":
webRequest.ContentType = header.Value as string;
break;
case "Accept":
webRequest.Accept = header.Value as string;
break;
default:
webRequest.Headers.Add(header.Key, Convert.ToString(header.Value));
break;
}
}
if (!headers.ContainsKey("Content-Type"))
{
webRequest.ContentType = "application/octet-stream";
}
if (binaryContainer != null)
{
using var requestStream = webRequest.GetRequestStream();
binaryContainer.WriteToStream(requestStream);
}
}
/// <summary>
/// Post content HttpWebRequest
/// </summary>
/// <param name="webRequest">HttpWebRequest to write the multipart form data to</param>
/// <param name="headers">IDictionary with the headers</param>
/// <param name="jsonString">string</param>
public static void Post(HttpWebRequest webRequest, IDictionary<string, object> headers, string jsonString)
{
if (headers != null)
{
foreach (var header in headers)
{
switch (header.Key)
{
case "Content-Type":
webRequest.ContentType = header.Value as string;
break;
case "Accept":
webRequest.Accept = header.Value as string;
break;
default:
webRequest.Headers.Add(header.Key, Convert.ToString(header.Value));
break;
}
}
if (!headers.ContainsKey("Content-Type"))
{
webRequest.ContentType = "application/json";
}
}
else
{
webRequest.ContentType = "application/json";
}
if (jsonString != null)
{
using var requestStream = webRequest.GetRequestStream();
using var streamWriter = new StreamWriter(requestStream);
streamWriter.Write(jsonString);
}
}
/// <summary> /// <summary>
/// Write Multipart Form Data to the HttpListenerResponse /// Write Multipart Form Data to the HttpListenerResponse
/// </summary> /// </summary>

View file

@ -51,7 +51,6 @@ namespace GreenshotPlugin.Core.OAuth {
// Use the returned code to get a refresh code // Use the returned code to get a refresh code
{ Code, settings.Code }, { Code, settings.Code },
{ ClientId, settings.ClientId }, { ClientId, settings.ClientId },
{ RedirectUri, settings.RedirectUrl },
{ ClientSecret, settings.ClientSecret }, { ClientSecret, settings.ClientSecret },
{ GrantType, AuthorizationCode } { GrantType, AuthorizationCode }
}; };
@ -194,29 +193,29 @@ namespace GreenshotPlugin.Core.OAuth {
} }
/// <summary> /// <summary>
/// Authenticate by using the mode specified in the settings /// Authorize by using the mode specified in the settings
/// </summary> /// </summary>
/// <param name="settings">OAuth2Settings</param> /// <param name="settings">OAuth2Settings</param>
/// <returns>false if it was canceled, true if it worked, exception if not</returns> /// <returns>false if it was canceled, true if it worked, exception if not</returns>
public static bool Authenticate(OAuth2Settings settings) { public static bool Authorize(OAuth2Settings settings) {
var completed = settings.AuthorizeMode switch var completed = settings.AuthorizeMode switch
{ {
OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings), OAuth2AuthorizeMode.LocalServer => AuthorizeViaLocalServer(settings),
OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings), OAuth2AuthorizeMode.EmbeddedBrowser => AuthorizeViaEmbeddedBrowser(settings),
OAuth2AuthorizeMode.JsonReceiver => AuthenticateViaDefaultBrowser(settings), OAuth2AuthorizeMode.JsonReceiver => AuthorizeViaDefaultBrowser(settings),
_ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."), _ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."),
}; };
return completed; return completed;
} }
/// <summary> /// <summary>
/// Authenticate via the default browser, via the Greenshot website. /// Authorize via the default browser, via the Greenshot website.
/// It will wait for a Json post. /// It will wait for a Json post.
/// If this works, return the code /// If this works, return the code
/// </summary> /// </summary>
/// <param name="settings">OAuth2Settings with the Auth / Token url etc</param> /// <param name="settings">OAuth2Settings with the Auth / Token url etc</param>
/// <returns>true if completed, false if canceled</returns> /// <returns>true if completed, false if canceled</returns>
private static bool AuthenticateViaDefaultBrowser(OAuth2Settings settings) private static bool AuthorizeViaDefaultBrowser(OAuth2Settings settings)
{ {
var codeReceiver = new LocalJsonReceiver(); var codeReceiver = new LocalJsonReceiver();
IDictionary<string, string> result = codeReceiver.ReceiveCode(settings); IDictionary<string, string> result = codeReceiver.ReceiveCode(settings);
@ -258,20 +257,21 @@ namespace GreenshotPlugin.Core.OAuth {
} }
if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code)) if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code))
{ {
settings.Code = code; settings.Code = code;
GenerateRefreshToken(settings); GenerateRefreshToken(settings);
return !string.IsNullOrEmpty(settings.AccessToken);
} }
return true; return true;
} }
/// <summary> /// <summary>
/// Authenticate via an embedded browser /// Authorize via an embedded browser
/// If this works, return the code /// If this works, return the code
/// </summary> /// </summary>
/// <param name="settings">OAuth2Settings with the Auth / Token url etc</param> /// <param name="settings">OAuth2Settings with the Auth / Token url etc</param>
/// <returns>true if completed, false if canceled</returns> /// <returns>true if completed, false if canceled</returns>
private static bool AuthenticateViaEmbeddedBrowser(OAuth2Settings settings) { private static bool AuthorizeViaEmbeddedBrowser(OAuth2Settings settings) {
if (string.IsNullOrEmpty(settings.CloudServiceName)) { if (string.IsNullOrEmpty(settings.CloudServiceName)) {
throw new ArgumentNullException(nameof(settings.CloudServiceName)); throw new ArgumentNullException(nameof(settings.CloudServiceName));
} }
@ -290,12 +290,12 @@ namespace GreenshotPlugin.Core.OAuth {
} }
/// <summary> /// <summary>
/// Authenticate via a local server by using the LocalServerCodeReceiver /// Authorize via a local server by using the LocalServerCodeReceiver
/// If this works, return the code /// If this works, return the code
/// </summary> /// </summary>
/// <param name="settings">OAuth2Settings with the Auth / Token url etc</param> /// <param name="settings">OAuth2Settings with the Auth / Token url etc</param>
/// <returns>true if completed</returns> /// <returns>true if completed</returns>
private static bool AuthenticateViaLocalServer(OAuth2Settings settings) { private static bool AuthorizeViaLocalServer(OAuth2Settings settings) {
var codeReceiver = new LocalServerCodeReceiver(); var codeReceiver = new LocalServerCodeReceiver();
IDictionary<string, string> result = codeReceiver.ReceiveCode(settings); IDictionary<string, string> result = codeReceiver.ReceiveCode(settings);
@ -335,7 +335,7 @@ namespace GreenshotPlugin.Core.OAuth {
public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) { public static void CheckAndAuthenticateOrRefresh(OAuth2Settings settings) {
// Get Refresh / Access token // Get Refresh / Access token
if (string.IsNullOrEmpty(settings.RefreshToken)) { if (string.IsNullOrEmpty(settings.RefreshToken)) {
if (!Authenticate(settings)) { if (!Authorize(settings)) {
throw new Exception("Authentication cancelled"); throw new Exception("Authentication cancelled");
} }
} }
@ -343,7 +343,7 @@ namespace GreenshotPlugin.Core.OAuth {
GenerateAccessToken(settings); GenerateAccessToken(settings);
// Get Refresh / Access token // Get Refresh / Access token
if (string.IsNullOrEmpty(settings.RefreshToken)) { if (string.IsNullOrEmpty(settings.RefreshToken)) {
if (!Authenticate(settings)) { if (!Authorize(settings)) {
throw new Exception("Authentication cancelled"); throw new Exception("Authentication cancelled");
} }
GenerateAccessToken(settings); GenerateAccessToken(settings);

View file

@ -137,6 +137,10 @@ namespace GreenshotPlugin.Core.OAuth
/// </summary> /// </summary>
public bool IsAccessTokenExpired { public bool IsAccessTokenExpired {
get { get {
if (AccessTokenExpires == default)
{
return false;
}
bool expired = true; bool expired = true;
if (!string.IsNullOrEmpty(AccessToken)) { if (!string.IsNullOrEmpty(AccessToken)) {
expired = DateTimeOffset.Now.AddSeconds(60) > AccessTokenExpires; expired = DateTimeOffset.Now.AddSeconds(60) > AccessTokenExpires;