mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
OAuth logins work, rebuilding the downloader now
This commit is contained in:
parent
30e4fcd40d
commit
c90181bcb4
@ -1,22 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Common.StatusFeed;
|
||||
using Wabbajack.Lib.LibCefHelpers;
|
||||
using Wabbajack.Lib.WebAutomation;
|
||||
using Wabbajack.Common.Serialization.Json;
|
||||
using Wabbajack.Lib.Http;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public abstract class AbstractIPS4OAuthDownloader<TDownloader, TState> : INeedsLogin, IDownloader, IWaitForWindowDownloader
|
||||
where TState : AbstractDownloadState, new()
|
||||
where TState : AbstractIPS4OAuthDownloader<TDownloader, TState>.State, new()
|
||||
where TDownloader : IDownloader
|
||||
{
|
||||
public AbstractIPS4OAuthDownloader(string clientID, Uri authEndpoint, Uri tokenEndpoint, string encryptedKeyName)
|
||||
@ -27,7 +27,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
EncryptedKeyName = encryptedKeyName;
|
||||
|
||||
TriggerLogin = ReactiveCommand.CreateFromTask(
|
||||
execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestOAuthLogin(ClientID, authEndpoint, tokenEndpoint, SiteName)).Task),
|
||||
execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestOAuthLogin(ClientID, authEndpoint, tokenEndpoint, SiteName, new []{"profile"}, EncryptedKeyName)).Task),
|
||||
canExecute: IsLoggedIn.Select(b => !b).ObserveOnGuiThread());
|
||||
ClearLogin = ReactiveCommand.CreateFromTask(
|
||||
execute: () => Utils.CatchAndLog(async () => await Utils.DeleteEncryptedJson(EncryptedKeyName)),
|
||||
@ -46,20 +46,122 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public IObservable<string>? MetaInfo { get; }
|
||||
public abstract Uri SiteURL { get; }
|
||||
public virtual Uri? IconUri { get; }
|
||||
public Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false)
|
||||
public Client? AuthedClient { get; set; }
|
||||
|
||||
private bool _isPrepared = false;
|
||||
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false)
|
||||
{
|
||||
if (archiveINI.General != null && archiveINI.General.directURL != null)
|
||||
{
|
||||
var parsed = new Uri(archiveINI.General.directURL);
|
||||
var fileID = parsed.AbsolutePath.Split("/").Last().Split("-").First();
|
||||
var modID = HttpUtility.ParseQueryString(parsed.Query).Get("r");
|
||||
|
||||
if (!long.TryParse(fileID, out var fileIDParsed))
|
||||
return null;
|
||||
if (modID != null && !long.TryParse(modID, out var modIDParsed))
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Prepare()
|
||||
public async Task Prepare()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
if (_isPrepared) return;
|
||||
AuthedClient = (await GetAuthedClient()) ?? throw new Exception($"Not logged into {SiteName}");
|
||||
_isPrepared = true;
|
||||
}
|
||||
|
||||
public Task WaitForNextRequestWindow()
|
||||
|
||||
private async Task<Http.Client?> GetAuthedClient()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!Utils.HaveEncryptedJson(EncryptedKeyName))
|
||||
return null;
|
||||
|
||||
var data = await Utils.FromEncryptedJson<OAuthResultState>(EncryptedKeyName);
|
||||
await data.Refresh();
|
||||
var client = new Http.Client();
|
||||
client.Headers.Add(("Authorization", $"Bearer {data.AccessToken}"));
|
||||
return client;
|
||||
}
|
||||
|
||||
public async Task WaitForNextRequestWindow()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public abstract class State : AbstractDownloadState
|
||||
{
|
||||
public long FileID { get; set; }
|
||||
public long? ModID { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
[JsonName("OAuthResultState")]
|
||||
public class OAuthResultState
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; } = "";
|
||||
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; } = "";
|
||||
|
||||
[JsonProperty("expires_in")]
|
||||
public long ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("refresh_token")]
|
||||
public string RefreshToken { get; set; } = "";
|
||||
|
||||
[JsonProperty("scope")]
|
||||
public string Scope { get; set; } = "";
|
||||
|
||||
[JsonProperty("authorization_code")]
|
||||
public string AuthorizationCode { get; set; } = "";
|
||||
|
||||
[JsonProperty("token_endpoint")]
|
||||
public Uri? TokenEndpoint { get; set; }
|
||||
|
||||
[JsonProperty("expires_at")]
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
|
||||
[JsonProperty("client_id")]
|
||||
public string ClientID { get; set; } = "";
|
||||
|
||||
internal void FillInData(string authCode, RequestOAuthLogin oa)
|
||||
{
|
||||
AuthorizationCode = authCode;
|
||||
TokenEndpoint = oa.TokenEndpoint;
|
||||
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(ExpiresIn);
|
||||
ClientID = oa.ClientID;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> Refresh()
|
||||
{
|
||||
if (ExpiresAt > DateTime.UtcNow + TimeSpan.FromHours(6))
|
||||
return true;
|
||||
|
||||
var client = new Http.Client();
|
||||
var formData = new KeyValuePair<string?, string?>[]
|
||||
{
|
||||
new ("grant_type", "refresh_token"),
|
||||
new ("refresh_token", RefreshToken),
|
||||
new ("client_id", ClientID)
|
||||
};
|
||||
using var response = await client.PostAsync(TokenEndpoint!.ToString(), new FormUrlEncodedContent(formData.ToList()));
|
||||
var responseData = (await response.Content.ReadAsStringAsync()).FromJsonString<OAuthResultState>();
|
||||
|
||||
AccessToken = responseData.AccessToken;
|
||||
ExpiresIn = responseData.ExpiresIn;
|
||||
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(ExpiresIn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class RequestOAuthLogin : AUserIntervention
|
||||
@ -68,24 +170,44 @@ namespace Wabbajack.Lib.Downloaders
|
||||
public Uri AuthorizationEndpoint { get; }
|
||||
public Uri TokenEndpoint { get; }
|
||||
public string SiteName { get; }
|
||||
|
||||
public string[] Scopes { get; }
|
||||
|
||||
public RequestOAuthLogin(string clientID, Uri authEndpoint, Uri tokenEndpoint, string siteName)
|
||||
public RequestOAuthLogin(string clientID, Uri authEndpoint, Uri tokenEndpoint, string siteName, IEnumerable<string> scopes, string key)
|
||||
{
|
||||
ClientID = clientID;
|
||||
AuthorizationEndpoint = authEndpoint;
|
||||
TokenEndpoint = tokenEndpoint;
|
||||
SiteName = siteName;
|
||||
Scopes = scopes.ToArray();
|
||||
EncryptedDataKey = key;
|
||||
}
|
||||
|
||||
public string EncryptedDataKey { get; set; }
|
||||
|
||||
public override string ShortDescription => $"Getting {SiteName} Login";
|
||||
public override string ExtendedDescription { get; } = string.Empty;
|
||||
|
||||
private readonly TaskCompletionSource<string> _source = new ();
|
||||
public Task<string> Task => _source.Task;
|
||||
private readonly TaskCompletionSource<OAuthResultState> _source = new ();
|
||||
public Task<OAuthResultState> Task => _source.Task;
|
||||
|
||||
public void Resume(string authToken)
|
||||
public async Task Resume(string authCode)
|
||||
{
|
||||
Handled = true;
|
||||
_source.SetResult(authToken);
|
||||
|
||||
var client = new Http.Client();
|
||||
var formData = new KeyValuePair<string?, string?>[]
|
||||
{
|
||||
new ("grant_type", "authorization_code"),
|
||||
new ("code", authCode),
|
||||
new ("client_id", ClientID)
|
||||
};
|
||||
using var response = await client.PostAsync(TokenEndpoint.ToString(), new FormUrlEncodedContent(formData.ToList()));
|
||||
var responseData = (await response.Content.ReadAsStringAsync()).FromJsonString<OAuthResultState>();
|
||||
responseData.FillInData(authCode, this);
|
||||
|
||||
await responseData.ToEcryptedJson(EncryptedDataKey);
|
||||
_source.SetResult(new OAuthResultState());
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
@ -93,5 +215,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
Handled = true;
|
||||
_source.TrySetCanceled();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
}
|
||||
|
||||
|
||||
public class State : AbstractDownloadState
|
||||
public class State : AbstractIPS4OAuthDownloader<VectorPlexusDownloader, VectorPlexusDownloader.State>.State
|
||||
{
|
||||
public override object[] PrimaryKey { get; } = Array.Empty<object>();
|
||||
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
||||
|
@ -396,7 +396,7 @@ namespace Wabbajack.Test
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
[Fact]
|
||||
public async Task VectorPlexusDownload()
|
||||
{
|
||||
@ -420,7 +420,7 @@ namespace Wabbajack.Test
|
||||
|
||||
Assert.Equal("Cheese for Everyone!", await filename.Path.ReadAllTextAsync());
|
||||
|
||||
}*/
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task YandexDownloader()
|
||||
|
@ -113,12 +113,23 @@ namespace Wabbajack
|
||||
vm.Instructions = "Please log in and allow Wabbajack to access your account";
|
||||
|
||||
var wrapper = new CefSharpWrapper(vm.Browser);
|
||||
await wrapper.NavigateTo(new Uri(oa.AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}"));
|
||||
var scopes = string.Join("&", oa.Scopes.Select(s => $"scope={s}"));
|
||||
var state = Guid.NewGuid().ToString();
|
||||
await wrapper.NavigateTo(new Uri(oa.AuthorizationEndpoint + $"?response_type=code&client_id={oa.ClientID}&state={state}&{scopes}"));
|
||||
|
||||
Helpers.SchemeHandler = (browser, frame, _, request) =>
|
||||
{
|
||||
var req = new Uri(request.Url);
|
||||
var parsed = HttpUtility.ParseQueryString(req.Query);
|
||||
if (parsed.Contains("state"))
|
||||
{
|
||||
if (parsed.Get("state") != state)
|
||||
{
|
||||
Utils.Log("Bad OAuth state, state, this shouldn't happen");
|
||||
oa.Cancel();
|
||||
return new ResourceHandler();
|
||||
}
|
||||
}
|
||||
if (parsed.Contains("code"))
|
||||
{
|
||||
oa.Resume(parsed.Get("code"));
|
||||
@ -127,7 +138,6 @@ namespace Wabbajack
|
||||
{
|
||||
oa.Cancel();
|
||||
}
|
||||
|
||||
return new ResourceHandler();
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user