Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 68 additions & 48 deletions src/GitHub.Api/Application/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@ public static IApiClient Create(UriString repositoryUrl, IKeychain keychain)
new GitHubClient(AppConfiguration.ProductHeader, credentialStore, hostAddress.ApiUri));
}

private static readonly Unity.ILogging logger = Unity.Logging.GetLogger<ApiClient>();
private static readonly ILogging logger = 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;
Expand Down Expand Up @@ -72,11 +68,10 @@ public async Task CreateRepository(NewRepository newRepository, Action<Octokit.R
}
}

public async Task GetOrganizations(Action<IList<Organization>> callback)
public async Task GetOrganizations(Action<IList<Organization>> onSuccess, Action<Exception> onError = null)
{
Guard.ArgumentNotNull(callback, "callback");
var organizations = await GetOrganizationInternal();
callback(organizations);
Guard.ArgumentNotNull(onSuccess, nameof(onSuccess));
await GetOrganizationInternal(onSuccess, onError);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the ...Internal method that is...

}

public async Task LoadKeychain(Action<bool> callback)
Expand Down Expand Up @@ -195,10 +190,8 @@ public async Task<bool> ContinueLoginAsync(LoginResult loginResult, Func<LoginRe
{
logger.Trace("Creating repository");

if (!await LoadKeychainInternal())
{
throw new InvalidOperationException("The keychain did not load");
}
await ValidateKeychain();
await ValidateCurrentUserInternal();

Octokit.Repository repository;
if (!string.IsNullOrEmpty(organization))
Expand All @@ -224,66 +217,66 @@ public async Task<bool> ContinueLoginAsync(LoginResult loginResult, Func<LoginRe
}
}

private async Task<IList<Organization>> GetOrganizationInternal()
private async Task GetOrganizationInternal(Action<IList<Organization>> onSuccess, Action<Exception> onError = null)
{
try
{
logger.Trace("Getting Organizations");

if (!await LoadKeychainInternal())
{
return new List<Organization>();
}
await ValidateKeychain();
await ValidateCurrentUserInternal();

var organizations = await githubClient.Organization.GetAllForCurrent();

logger.Trace("Obtained {0} Organizations", organizations?.Count.ToString() ?? "NULL");

if (organizations != null)
{
organizationsCache = organizations.ToArray();
onSuccess(organizations.ToArray());
}
}
catch(Exception ex)
{
logger.Error(ex, "Error Getting Organizations");
throw;
onError?.Invoke(ex);
}

return organizationsCache;
}

private async Task<Octokit.User> GetCurrentUserInternal()
{
try
{
logger.Trace("Getting Organizations");

if (!await LoadKeychainInternal())
{
return null;
}
logger.Trace("Getting Current User");
await ValidateKeychain();

userCache = await githubClient.User.Current();
return await githubClient.User.Current();
}
catch(Exception ex)
catch (Exception ex)
{
logger.Error(ex, "Error Getting Current User");
throw;
}
}

private async Task ValidateCurrentUserInternal()
{
logger.Trace("Validating User");
var apiUsername = await GetCurrentUserInternal();
var cachedUsername = keychain.Connections.First().Username;

return userCache;
if (apiUsername.Name != cachedUsername)
{
throw new TokenUsernameMismatchException();
}
}

private async Task<bool> LoadKeychainInternal()
{
logger.Trace("LoadKeychainInternal");

if (keychain.HasKeys)
{
if (!keychain.NeedsLoad)
{
logger.Trace("LoadKeychainInternal: Has keys does not need load");
logger.Trace("LoadKeychainInternal: Previously Loaded");
return true;
}

Expand All @@ -293,6 +286,8 @@ private async Task<bool> LoadKeychainInternal()
var uriString = keychain.Connections.First().Host;
var keychainAdapter = await keychain.Load(uriString);

logger.Trace("LoadKeychainInternal: Loaded");

return keychainAdapter.OctokitCredentials != Credentials.Anonymous;
}

Expand All @@ -301,23 +296,48 @@ private async Task<bool> LoadKeychainInternal()
return false;
}

public async Task<bool> ValidateCredentials()
private async Task ValidateKeychain()
{
try
if (!await LoadKeychainInternal())
{
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;
throw new KeychainEmptyException();
}
return true;
}
}

class ApiClientException : Exception
{
public ApiClientException()
{ }

public ApiClientException(string message) : base(message)
{ }

public ApiClientException(string message, Exception innerException) : base(message, innerException)
{ }
}

class TokenUsernameMismatchException : ApiClientException
{
public TokenUsernameMismatchException()
{ }

public TokenUsernameMismatchException(string message) : base(message)
{ }

public TokenUsernameMismatchException(string message, Exception innerException) : base(message, innerException)
{ }
}

class KeychainEmptyException : ApiClientException
{
public KeychainEmptyException()
{ }

public KeychainEmptyException(string message) : base(message)
{ }

public KeychainEmptyException(string message, Exception innerException) : base(message, innerException)
{ }
}
}
3 changes: 1 addition & 2 deletions src/GitHub.Api/Application/IApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ interface IApiClient
HostAddress HostAddress { get; }
UriString OriginalUrl { get; }
Task CreateRepository(NewRepository newRepository, Action<Octokit.Repository, Exception> callback, string organization = null);
Task GetOrganizations(Action<IList<Organization>> callback);
Task GetOrganizations(Action<IList<Organization>> onSuccess, Action<Exception> onError = null);
Task Login(string username, string password, Action<LoginResult> need2faCode, Action<bool, string> result);
Task ContinueLogin(LoginResult loginResult, string code);
Task<bool> LoginAsync(string username, string password, Func<LoginResult, string> need2faCode);
Task<bool> ValidateCredentials();
Task Logout(UriString host);
Task GetCurrentUser(Action<Octokit.User> callback);
Task LoadKeychain(Action<bool> callback);
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.Api/Authentication/Keychain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task<IKeychainAdapter> Load(UriString host)
{
if (keychainItem.Username != cachedConnection.Username)
{
logger.Warning("Keychain Username: {0} does not match; Hopefully it works", keychainItem.Username);
logger.Warning("Keychain Username:\"{0}\" does not match cached Username:\"{1}\"; Hopefully it works", keychainItem.Username, cachedConnection.Username);
}

logger.Trace("Loaded from Credential Manager Host:\"{0}\" Username:\"{1}\"", keychainItem.Host, keychainItem.Username);
Expand Down
18 changes: 18 additions & 0 deletions src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,24 @@ private void LoadOrganizations()
owners = new[] { OwnersDefaultText, username }.Union(organizationLogins).ToArray();

isBusy = false;
}, exception => {
var tokenUsernameMismatchException = exception as TokenUsernameMismatchException;
if (tokenUsernameMismatchException != null)
{
//This is a specific case

Logger.Error(exception, "Token Username Mismatch");
return;
}

var keychainEmptyException = exception as KeychainEmptyException;
if (keychainEmptyException != null)
{
Logger.Error(exception, "Keychain empty");
return;
}

Logger.Error(exception, "Unhandled Exception Type");
});
});
}
Expand Down