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

@ -21,7 +21,6 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using GreenshotPlugin.Controls;
using GreenshotPlugin.Core;
@ -102,20 +101,16 @@ namespace GreenshotDropboxPlugin {
public bool Upload(ICaptureDetails captureDetails, ISurface surfaceToUpload, out string uploadUrl) {
uploadUrl = null;
SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(_config.UploadFormat, _config.UploadJpegQuality, false);
try {
string dropboxUrl = null;
try
{
bool result = false;
new PleaseWaitForm().ShowAndWait("Dropbox", Language.GetString("dropbox", LangKey.communication_wait),
delegate
{
string filename = Path.GetFileName(FilenameHelper.GetFilename(_config.UploadFormat, captureDetails));
dropboxUrl = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, filename);
result = DropboxUtils.UploadToDropbox(surfaceToUpload, outputSettings, captureDetails);
}
);
if (dropboxUrl == null) {
return false;
}
uploadUrl = dropboxUrl;
return true;
return result;
} catch (Exception e) {
Log.Error(e);
MessageBox.Show(Language.GetString("dropbox", LangKey.upload_failure) + " " + e.Message);

View file

@ -20,11 +20,13 @@
*/
using System;
using System.Collections.Generic;
using System.IO;
using GreenshotPlugin.Core;
using GreenshotPlugin.Core.OAuth;
using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Plugin;
using Newtonsoft.Json;
namespace GreenshotDropboxPlugin {
/// <summary>
@ -37,11 +39,11 @@ namespace GreenshotDropboxPlugin {
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
{
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",
RedirectUrl = "https://getgreenshot.org/authorize/dropbox",
CloudServiceName = "Dropbox",
@ -55,20 +57,28 @@ namespace GreenshotDropboxPlugin {
try
{
string filename = Path.GetFileName(FilenameHelper.GetFilename(DropboxConfig.UploadFormat, captureDetails));
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 },
{ "mute", true},
{ "path", filename}
{ "mute", true },
{ "path", "/" + filename.Replace(Path.DirectorySeparatorChar, '\\')}
};
var webRequest = OAuth2Helper.CreateOAuth2WebRequest(HTTPMethod.POST, "https://api.dropbox.com//2/files/upload", oauth2Settings);
NetworkHelper.WriteMultipartFormData(webRequest, parameters);
var response = NetworkHelper.GetResponseAsString(webRequest);
Log.DebugFormat("Upload response: {0}", response);
} catch (Exception ex) {
IDictionary<string, object> headers = new Dictionary<string, object>
{
{ "Dropbox-API-Arg", JsonConvert.SerializeObject(arguments)}
};
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);
throw;
} finally {
@ -78,7 +88,6 @@ namespace GreenshotDropboxPlugin {
DropboxConfig.IsDirty = true;
IniConfig.Save();
}
return null;
}
}
}

View file

@ -358,6 +358,84 @@ namespace GreenshotPlugin.Core {
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>
/// Write Multipart Form Data to the HttpListenerResponse
/// </summary>

View file

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

View file

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