mirror of
https://github.com/greenshot/greenshot
synced 2025-08-22 14:24:43 -07:00
Removed the title monitoring, as it was dependent on to many factory (Edge cutting the title).
This commit is contained in:
parent
8dee08bad0
commit
2cacd80992
6 changed files with 1095 additions and 921 deletions
1
Greenshot/Forms/AboutForm.Designer.cs
generated
1
Greenshot/Forms/AboutForm.Designer.cs
generated
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Greenshot.Helpers;
|
using Greenshot.Helpers;
|
||||||
|
using GreenshotPlugin.Core;
|
||||||
|
|
||||||
namespace Greenshot.Forms {
|
namespace Greenshot.Forms {
|
||||||
partial class AboutForm {
|
partial class AboutForm {
|
||||||
|
|
|
@ -169,11 +169,11 @@ namespace GreenshotImgurPlugin {
|
||||||
{
|
{
|
||||||
AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}",
|
AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}",
|
||||||
TokenUrl = "https://api.imgur.com/oauth2/token",
|
TokenUrl = "https://api.imgur.com/oauth2/token",
|
||||||
RedirectUrl = "https://getgreenshot.org/oauth/imgur",
|
RedirectUrl = "https://getgreenshot.org/authorize/imgur",
|
||||||
CloudServiceName = "Imgur",
|
CloudServiceName = "Imgur",
|
||||||
ClientId = ImgurCredentials.CONSUMER_KEY,
|
ClientId = ImgurCredentials.CONSUMER_KEY,
|
||||||
ClientSecret = ImgurCredentials.CONSUMER_SECRET,
|
ClientSecret = ImgurCredentials.CONSUMER_SECRET,
|
||||||
AuthorizeMode = OAuth2AuthorizeMode.MonitorTitle,
|
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
|
||||||
RefreshToken = Config.RefreshToken,
|
RefreshToken = Config.RefreshToken,
|
||||||
AccessToken = Config.AccessToken,
|
AccessToken = Config.AccessToken,
|
||||||
AccessTokenExpires = Config.AccessTokenExpires
|
AccessTokenExpires = Config.AccessTokenExpires
|
||||||
|
|
File diff suppressed because it is too large
Load diff
191
GreenshotPlugin/Core/OAuth/LocalJsonReceiver.cs
Normal file
191
GreenshotPlugin/Core/OAuth/LocalJsonReceiver.cs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Greenshot - a free and open source screenshot tool
|
||||||
|
* Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom
|
||||||
|
*
|
||||||
|
* For more information see: http://getgreenshot.org/
|
||||||
|
* The Greenshot project is hosted on GitHub https://github.com/greenshot/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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using log4net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace GreenshotPlugin.Core.OAuth
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// OAuth 2.0 verification code receiver that runs a local server on a free port
|
||||||
|
/// and waits for a call with the authorization verification code.
|
||||||
|
/// </summary>
|
||||||
|
public class LocalJsonReceiver
|
||||||
|
{
|
||||||
|
private static readonly ILog Log = LogManager.GetLogger(typeof(LocalJsonReceiver));
|
||||||
|
private readonly ManualResetEvent _ready = new ManualResetEvent(true);
|
||||||
|
private IDictionary<string, string> _returnValues;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The url format for the website to post to. Expects one port parameter.
|
||||||
|
/// Default: http://localhost:{0}/authorize/
|
||||||
|
/// </summary>
|
||||||
|
public string ListeningUrlFormat { get; set; } = "http://localhost:{0}/authorize/";
|
||||||
|
|
||||||
|
private string _listeningUri;
|
||||||
|
/// <summary>
|
||||||
|
/// The URL where the server is listening
|
||||||
|
/// </summary>
|
||||||
|
public string ListeningUri {
|
||||||
|
get {
|
||||||
|
if (string.IsNullOrEmpty(_listeningUri))
|
||||||
|
{
|
||||||
|
_listeningUri = string.Format(ListeningUrlFormat, GetRandomUnusedPort());
|
||||||
|
}
|
||||||
|
return _listeningUri;
|
||||||
|
}
|
||||||
|
set => _listeningUri = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This action is called when the URI must be opened, default is just to run Process.Start
|
||||||
|
/// </summary>
|
||||||
|
public Action<string> OpenUriAction
|
||||||
|
{
|
||||||
|
set;
|
||||||
|
get;
|
||||||
|
} = authorizationUrl =>
|
||||||
|
{
|
||||||
|
Log.DebugFormat("Open a browser with: {0}", authorizationUrl);
|
||||||
|
using var process = Process.Start(authorizationUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timeout for waiting for the website to respond
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(4);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The OAuth code receiver
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oauth2Settings">OAuth2Settings</param>
|
||||||
|
/// <returns>Dictionary with values</returns>
|
||||||
|
public IDictionary<string, string> ReceiveCode(OAuth2Settings oauth2Settings) {
|
||||||
|
using var listener = new HttpListener();
|
||||||
|
// Make sure the port is stored in the state, so the website can process this.
|
||||||
|
oauth2Settings.State = new Uri(ListeningUri).Port.ToString();
|
||||||
|
listener.Prefixes.Add(ListeningUri);
|
||||||
|
try {
|
||||||
|
listener.Start();
|
||||||
|
_ready.Reset();
|
||||||
|
|
||||||
|
listener.BeginGetContext(ListenerCallback, listener);
|
||||||
|
OpenUriAction(oauth2Settings.FormattedAuthUrl);
|
||||||
|
_ready.WaitOne(Timeout, true);
|
||||||
|
} catch (Exception) {
|
||||||
|
// Make sure we can clean up, also if the thead is aborted
|
||||||
|
_ready.Set();
|
||||||
|
throw;
|
||||||
|
} finally {
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (request.HasEntityBody)
|
||||||
|
{
|
||||||
|
// Process the body
|
||||||
|
using var body = request.InputStream;
|
||||||
|
using var reader = new StreamReader(body, request.ContentEncoding);
|
||||||
|
using var jsonTextReader = new JsonTextReader(reader);
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
_returnValues = serializer.Deserialize<Dictionary<string, string>>(jsonTextReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the response.
|
||||||
|
using (HttpListenerResponse response = context.Response)
|
||||||
|
{
|
||||||
|
if (request.HttpMethod == "OPTIONS")
|
||||||
|
{
|
||||||
|
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
|
||||||
|
response.AddHeader("Access-Control-Allow-Methods", "POST");
|
||||||
|
response.AddHeader("Access-Control-Max-Age", "1728000");
|
||||||
|
}
|
||||||
|
|
||||||
|
response.AppendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
if (request.HasEntityBody)
|
||||||
|
{
|
||||||
|
response.ContentType = "application/json";
|
||||||
|
// currently only return the version, more can be added later
|
||||||
|
string jsonContent = "{\"version\": \"" + EnvironmentInfo.GetGreenshotVersion(true) + "\"}";
|
||||||
|
|
||||||
|
// Write a "close" response.
|
||||||
|
byte[] buffer = Encoding.UTF8.GetBytes(jsonContent);
|
||||||
|
// Write to response stream.
|
||||||
|
response.ContentLength64 = buffer.Length;
|
||||||
|
using var stream = response.OutputStream;
|
||||||
|
stream.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_returnValues != null)
|
||||||
|
{
|
||||||
|
_ready.Set();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Make sure the next request is processed
|
||||||
|
listener.BeginGetContext(ListenerCallback, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a random, unused port.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>port to use</returns>
|
||||||
|
private static int GetRandomUnusedPort() {
|
||||||
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||||
|
try {
|
||||||
|
listener.Start();
|
||||||
|
return ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||||
|
} finally {
|
||||||
|
listener.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,7 @@ namespace GreenshotPlugin.Core.OAuth
|
||||||
public enum OAuth2AuthorizeMode {
|
public enum OAuth2AuthorizeMode {
|
||||||
Unknown, // Will give an exception, caller needs to specify another value
|
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
|
LocalServer, // Will specify a redirect URL to http://localhost:port/authorize, while having a HttpListener
|
||||||
MonitorTitle, // Will monitor for title changes, the title needs the status and query params
|
JsonReceiver, // Will start a local HttpListener and wait for a Json post
|
||||||
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
|
EmbeddedBrowser // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,12 +21,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
|
||||||
using GreenshotPlugin.Controls;
|
using GreenshotPlugin.Controls;
|
||||||
using GreenshotPlugin.Hooking;
|
|
||||||
|
|
||||||
namespace GreenshotPlugin.Core.OAuth {
|
namespace GreenshotPlugin.Core.OAuth {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -134,7 +131,7 @@ namespace GreenshotPlugin.Core.OAuth {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings
|
/// Go out and retrieve a new access token via refresh-token with the TokenUrl in the settings
|
||||||
/// Will upate the access token, refresh token, expire date
|
/// Will update the access token, refresh token, expire date
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings"></param>
|
/// <param name="settings"></param>
|
||||||
public static void GenerateAccessToken(OAuth2Settings settings) {
|
public static void GenerateAccessToken(OAuth2Settings settings) {
|
||||||
|
@ -159,22 +156,23 @@ namespace GreenshotPlugin.Core.OAuth {
|
||||||
// "token_type":"Bearer",
|
// "token_type":"Bearer",
|
||||||
|
|
||||||
IDictionary<string, object> accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult);
|
IDictionary<string, object> accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult);
|
||||||
if (accessTokenResult.ContainsKey("error")) {
|
if (accessTokenResult.ContainsKey("error"))
|
||||||
if ("invalid_grant" == (string)accessTokenResult["error"]) {
|
{
|
||||||
|
if ("invalid_grant" == (string)accessTokenResult["error"]) {
|
||||||
// Refresh token has also expired, we need a new one!
|
// Refresh token has also expired, we need a new one!
|
||||||
settings.RefreshToken = null;
|
settings.RefreshToken = null;
|
||||||
settings.AccessToken = null;
|
settings.AccessToken = null;
|
||||||
settings.AccessTokenExpires = DateTimeOffset.MinValue;
|
settings.AccessTokenExpires = DateTimeOffset.MinValue;
|
||||||
settings.Code = null;
|
settings.Code = null;
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
if (accessTokenResult.ContainsKey("error_description")) {
|
|
||||||
throw new Exception($"{accessTokenResult["error"]} - {accessTokenResult["error_description"]}");
|
|
||||||
} else {
|
|
||||||
throw new Exception((string)accessTokenResult["error"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (accessTokenResult.ContainsKey("error_description")) {
|
||||||
|
throw new Exception($"{accessTokenResult["error"]} - {accessTokenResult["error_description"]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception((string)accessTokenResult["error"]);
|
||||||
|
}
|
||||||
|
|
||||||
if (accessTokenResult.ContainsKey(AccessToken))
|
if (accessTokenResult.ContainsKey(AccessToken))
|
||||||
{
|
{
|
||||||
|
@ -205,76 +203,62 @@ namespace GreenshotPlugin.Core.OAuth {
|
||||||
{
|
{
|
||||||
OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings),
|
OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings),
|
||||||
OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings),
|
OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings),
|
||||||
OAuth2AuthorizeMode.MonitorTitle => AuthenticateViaDefaultBrowser(settings),
|
OAuth2AuthorizeMode.JsonReceiver => AuthenticateViaDefaultBrowser(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, using the browser title.
|
/// Authenticate via the default browser, via the Greenshot website.
|
||||||
|
/// 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 AuthenticateViaDefaultBrowser(OAuth2Settings settings)
|
||||||
{
|
{
|
||||||
var monitor = new WindowsTitleMonitor();
|
var codeReceiver = new LocalJsonReceiver();
|
||||||
|
IDictionary<string, string> result = codeReceiver.ReceiveCode(settings);
|
||||||
|
|
||||||
string error = null;
|
foreach (var key in result.Keys)
|
||||||
var fields = new HashSet<string>();
|
|
||||||
int nrOfFields = 100;
|
|
||||||
|
|
||||||
monitor.TitleChangeEvent += args =>
|
|
||||||
{
|
{
|
||||||
if (!args.Title.Contains(settings.State))
|
switch (key)
|
||||||
{
|
{
|
||||||
return;
|
case AccessToken:
|
||||||
|
settings.AccessToken = result[key];
|
||||||
|
break;
|
||||||
|
case ExpiresIn:
|
||||||
|
if (int.TryParse(result[key], out var seconds))
|
||||||
|
{
|
||||||
|
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RefreshToken:
|
||||||
|
settings.RefreshToken = result[key];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var title = args.Title;
|
if (result.TryGetValue("error", out var error))
|
||||||
title = title.Substring(0,title.IndexOf(' '));
|
{
|
||||||
|
if (result.TryGetValue("error_description", out var errorDescription))
|
||||||
var parameters = NetworkHelper.ParseQueryString(title);
|
|
||||||
|
|
||||||
if (parameters.ContainsKey("nr"))
|
|
||||||
{
|
{
|
||||||
nrOfFields = int.Parse(parameters["nr"]);
|
throw new Exception(errorDescription);
|
||||||
}
|
}
|
||||||
|
if ("access_denied" == error)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Access denied");
|
||||||
|
}
|
||||||
|
throw new Exception(error);
|
||||||
|
}
|
||||||
|
if (result.TryGetValue(Code, out var code) && !string.IsNullOrEmpty(code))
|
||||||
|
{
|
||||||
|
settings.Code = code;
|
||||||
|
GenerateRefreshToken(settings);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var key in parameters.Keys)
|
return true;
|
||||||
{
|
|
||||||
fields.Add(key);
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case AccessToken:
|
|
||||||
settings.AccessToken = parameters[key];
|
|
||||||
break;
|
|
||||||
case ExpiresIn:
|
|
||||||
if (int.TryParse(parameters[key], out var seconds))
|
|
||||||
{
|
|
||||||
settings.AccessTokenExpires = DateTimeOffset.Now.AddSeconds(seconds);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RefreshToken:
|
|
||||||
settings.RefreshToken = parameters[key];
|
|
||||||
break;
|
|
||||||
case Error:
|
|
||||||
error = parameters[key];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
using (var process = Process.Start(settings.FormattedAuthUrl))
|
|
||||||
{
|
|
||||||
while (nrOfFields > fields.Count)
|
|
||||||
{
|
|
||||||
// Have the thread process Forms events
|
|
||||||
Application.DoEvents();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
monitor.Dispose();
|
|
||||||
return error == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue