mirror of
https://github.com/greenshot/greenshot
synced 2025-08-22 06:23:24 -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 Greenshot.Helpers;
|
||||
using GreenshotPlugin.Core;
|
||||
|
||||
namespace Greenshot.Forms {
|
||||
partial class AboutForm {
|
||||
|
|
|
@ -169,11 +169,11 @@ namespace GreenshotImgurPlugin {
|
|||
{
|
||||
AuthUrlPattern = "https://api.imgur.com/oauth2/authorize?response_type=token&client_id={ClientId}&state={State}",
|
||||
TokenUrl = "https://api.imgur.com/oauth2/token",
|
||||
RedirectUrl = "https://getgreenshot.org/oauth/imgur",
|
||||
RedirectUrl = "https://getgreenshot.org/authorize/imgur",
|
||||
CloudServiceName = "Imgur",
|
||||
ClientId = ImgurCredentials.CONSUMER_KEY,
|
||||
ClientSecret = ImgurCredentials.CONSUMER_SECRET,
|
||||
AuthorizeMode = OAuth2AuthorizeMode.MonitorTitle,
|
||||
AuthorizeMode = OAuth2AuthorizeMode.JsonReceiver,
|
||||
RefreshToken = Config.RefreshToken,
|
||||
AccessToken = Config.AccessToken,
|
||||
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 {
|
||||
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, // Will monitor for title changes, the title needs the status and query params
|
||||
Pin, // Not implemented yet: Will ask the user to enter the shown PIN
|
||||
JsonReceiver, // Will start a local HttpListener and wait for a Json post
|
||||
EmbeddedBrowser // Will open into an embedded _browser (OAuthLoginForm), and catch the redirect
|
||||
}
|
||||
}
|
|
@ -21,12 +21,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using GreenshotPlugin.Controls;
|
||||
using GreenshotPlugin.Hooking;
|
||||
|
||||
namespace GreenshotPlugin.Core.OAuth {
|
||||
/// <summary>
|
||||
|
@ -134,7 +131,7 @@ namespace GreenshotPlugin.Core.OAuth {
|
|||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="settings"></param>
|
||||
public static void GenerateAccessToken(OAuth2Settings settings) {
|
||||
|
@ -159,22 +156,23 @@ namespace GreenshotPlugin.Core.OAuth {
|
|||
// "token_type":"Bearer",
|
||||
|
||||
IDictionary<string, object> accessTokenResult = JSONHelper.JsonDecode(accessTokenJsonResult);
|
||||
if (accessTokenResult.ContainsKey("error")) {
|
||||
if ("invalid_grant" == (string)accessTokenResult["error"]) {
|
||||
if (accessTokenResult.ContainsKey("error"))
|
||||
{
|
||||
if ("invalid_grant" == (string)accessTokenResult["error"]) {
|
||||
// Refresh token has also expired, we need a new one!
|
||||
settings.RefreshToken = null;
|
||||
settings.AccessToken = null;
|
||||
settings.AccessTokenExpires = DateTimeOffset.MinValue;
|
||||
settings.Code = null;
|
||||
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))
|
||||
{
|
||||
|
@ -205,76 +203,62 @@ namespace GreenshotPlugin.Core.OAuth {
|
|||
{
|
||||
OAuth2AuthorizeMode.LocalServer => AuthenticateViaLocalServer(settings),
|
||||
OAuth2AuthorizeMode.EmbeddedBrowser => AuthenticateViaEmbeddedBrowser(settings),
|
||||
OAuth2AuthorizeMode.MonitorTitle => AuthenticateViaDefaultBrowser(settings),
|
||||
OAuth2AuthorizeMode.JsonReceiver => AuthenticateViaDefaultBrowser(settings),
|
||||
_ => throw new NotImplementedException($"Authorize mode '{settings.AuthorizeMode}' is not 'yet' implemented."),
|
||||
};
|
||||
return completed;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// </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)
|
||||
{
|
||||
var monitor = new WindowsTitleMonitor();
|
||||
var codeReceiver = new LocalJsonReceiver();
|
||||
IDictionary<string, string> result = codeReceiver.ReceiveCode(settings);
|
||||
|
||||
string error = null;
|
||||
var fields = new HashSet<string>();
|
||||
int nrOfFields = 100;
|
||||
|
||||
monitor.TitleChangeEvent += args =>
|
||||
foreach (var key in result.Keys)
|
||||
{
|
||||
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;
|
||||
title = title.Substring(0,title.IndexOf(' '));
|
||||
|
||||
var parameters = NetworkHelper.ParseQueryString(title);
|
||||
|
||||
if (parameters.ContainsKey("nr"))
|
||||
if (result.TryGetValue("error", out var error))
|
||||
{
|
||||
if (result.TryGetValue("error_description", out var errorDescription))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue