-
Stanley Goldman authored5db22768
ApiClient.cs 10.77 KiB
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Octokit;
namespace GitHub.Unity
{
class ApiClient : IApiClient
{
public static IApiClient Create(UriString repositoryUrl, IKeychain keychain)
{
logger.Trace("Creating ApiClient: {0}", repositoryUrl);
var credentialStore = keychain.Connect(repositoryUrl);
var hostAddress = HostAddress.Create(repositoryUrl);
return new ApiClient(repositoryUrl, keychain,
new GitHubClient(AppConfiguration.ProductHeader, credentialStore, hostAddress.ApiUri));
}
private static readonly Unity.ILogging logger = Unity.Logging.GetLogger<ApiClient>();
public HostAddress HostAddress { get; }
public UriString OriginalUrl { get; }
private readonly IKeychain keychain;
private readonly IGitHubClient githubClient;
private readonly ILoginManager loginManager;
private static readonly SemaphoreSlim sem = new SemaphoreSlim(1);
IList<Organization> organizationsCache;
Octokit.User userCache;
string owner;
bool? isEnterprise;
public ApiClient(UriString hostUrl, IKeychain keychain, IGitHubClient githubClient)
{
Guard.ArgumentNotNull(hostUrl, nameof(hostUrl));
Guard.ArgumentNotNull(keychain, nameof(keychain));
Guard.ArgumentNotNull(githubClient, nameof(githubClient));
HostAddress = HostAddress.Create(hostUrl);
OriginalUrl = hostUrl;
this.keychain = keychain;
this.githubClient = githubClient;
loginManager = new LoginManager(keychain, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret);
}
public async Task Logout(UriString host)
{
await LogoutInternal(host);
}
private async Task LogoutInternal(UriString host)
{
await loginManager.Logout(host);
}
public async Task CreateRepository(NewRepository newRepository, Action<Octokit.Repository, Exception> callback, string organization = null)
{
Guard.ArgumentNotNull(callback, "callback");
await CreateRepositoryInternal(newRepository, callback, organization);
}
public async Task GetOrganizations(Action<IList<Organization>> callback)
{
Guard.ArgumentNotNull(callback, "callback");
var organizations = await GetOrganizationInternal();
callback(organizations);
}
public async Task GetCurrentUser(Action<Octokit.User> callback)
{
Guard.ArgumentNotNull(callback, "callback");
var user = await GetCurrentUserInternal();
callback(user);
}
public async Task GetCurrentUserAndOrganizations(Action<Octokit.User, IList<Organization>> callback)
{
Guard.ArgumentNotNull(callback, "callback");
await GetUsersAndOrganizationInternal(callback);
}
public async Task Login(string username, string password, Action<LoginResult> need2faCode, Action<bool, string> result)
{
Guard.ArgumentNotNull(need2faCode, "need2faCode");
Guard.ArgumentNotNull(result, "result");
LoginResultData res = null;
try
{
res = await loginManager.Login(OriginalUrl, githubClient, username, password);
}
catch (Exception ex)
{
logger.Warning(ex);
result(false, ex.Message);
return;
}
if (res.Code == LoginResultCodes.CodeRequired)
{
var resultCache = new LoginResult(res, result, need2faCode);
need2faCode(resultCache);
}
else
{
result(res.Code == LoginResultCodes.Success, res.Message);
}
}
public async Task ContinueLogin(LoginResult loginResult, string code)
{
LoginResultData result = null;
try
{
result = await loginManager.ContinueLogin(loginResult.Data, code);
}
catch (Exception ex)
{
loginResult.Callback(false, ex.Message);
return;
}
if (result.Code == LoginResultCodes.CodeFailed)
{
loginResult.TwoFACallback(new LoginResult(result, loginResult.Callback, loginResult.TwoFACallback));
}
loginResult.Callback(result.Code == LoginResultCodes.Success, result.Message);
}
public async Task<bool> LoginAsync(string username, string password, Func<LoginResult, string> need2faCode)
{
Guard.ArgumentNotNull(need2faCode, "need2faCode");
LoginResultData res = null;
try
{
res = await loginManager.Login(OriginalUrl, githubClient, username, password);
}
catch (Exception)
{
return false;
}
if (res.Code == LoginResultCodes.CodeRequired)
{
var resultCache = new LoginResult(res, null, null);
var code = need2faCode(resultCache);
return await ContinueLoginAsync(resultCache, need2faCode, code);
}
else
{
return res.Code == LoginResultCodes.Success;
}
}
public async Task<bool> ContinueLoginAsync(LoginResult loginResult, Func<LoginResult, string> need2faCode, string code)
{
LoginResultData result = null;
try
{
result = await loginManager.ContinueLogin(loginResult.Data, code);
}
catch (Exception)
{
return false;
}
if (result.Code == LoginResultCodes.CodeFailed)
{
var resultCache = new LoginResult(result, null, null);
code = need2faCode(resultCache);
if (String.IsNullOrEmpty(code))
return false;
return await ContinueLoginAsync(resultCache, need2faCode, code);
}
return result.Code == LoginResultCodes.Success;
}
private async Task CreateRepositoryInternal(NewRepository newRepository, Action<Octokit.Repository, Exception> callback, string organization)
{
try
{
logger.Trace("Creating repository");
if (!await EnsureKeychainLoaded())
{
callback(null, new Exception("Keychain Not Loaded"));
return;
}
Octokit.Repository repository;
if (!string.IsNullOrEmpty(organization))
{
logger.Trace("Creating repository for organization");
repository = await githubClient.Repository.Create(organization, newRepository);
}
else
{
logger.Trace("Creating repository for user");
repository = await githubClient.Repository.Create(newRepository);
}
logger.Trace("Created Repository");
callback(repository, null);
}
catch (Exception ex)
{
logger.Error(ex, "Error Creating Repository");
callback(null, ex);
}
}
private async Task<IList<Organization>> GetOrganizationInternal()
{
try
{
logger.Trace("Getting Organizations");
if (!await EnsureKeychainLoaded())
{
return null;
}
var organizations = await githubClient.Organization.GetAllForCurrent();
logger.Trace("Obtained {0} Organizations", organizations?.Count.ToString() ?? "NULL");
if (organizations != null)
{
organizationsCache = organizations.ToArray();
}
}
catch(Exception ex)
{
logger.Error(ex, "Error Getting Organizations");
throw;
}
return organizationsCache;
}
private async Task<Octokit.User> GetCurrentUserInternal()
{
try
{
logger.Trace("Getting Organizations");
if (!await EnsureKeychainLoaded())
{
return null;
}
userCache = await githubClient.User.Current();
}
catch(Exception ex)
{
logger.Error(ex, "Error Getting Current User");
throw;
}
return userCache;
}
private async Task GetUsersAndOrganizationInternal(Action<Octokit.User, IList<Organization>> callback)
{
if (!await EnsureKeychainLoaded())
{
callback(null, null);
return;
}
var currentUserInternal = GetCurrentUserInternal();
var organizationInternal = GetOrganizationInternal();
currentUserInternal.Start(TaskScheduler.Current);
organizationInternal.Start(TaskScheduler.Current);
callback(await currentUserInternal,await organizationInternal);
}
private async Task<bool> EnsureKeychainLoaded()
{
logger.Trace("EnsureKeychainLoaded");
if (keychain.HasKeys)
{
if (!keychain.NeedsLoad)
{
logger.Trace("EnsureKeychainLoaded: Has keys does not need load");
return true;
}
logger.Trace("EnsureKeychainLoaded: Loading");
var uriString = keychain.Connections.First().Host;
var keychainAdapter = await keychain.Load(uriString);
return keychainAdapter.OctokitCredentials != Credentials.Anonymous;
}
logger.Trace("EnsureKeychainLoaded: No keys to load");
return false;
}
public async Task<bool> ValidateCredentials()
{
try
{
var store = keychain.Connect(OriginalUrl);
if (store.OctokitCredentials != Credentials.Anonymous)
{
var credential = store.Credential;
await githubClient.Authorization.CheckApplicationAuthentication(ApplicationInfo.ClientId, credential.Token);
}
}
catch
{
return false;
}
return true;
}
}
}