diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs index 2ea877a3f..067c05c76 100644 --- a/src/GitHub.Api/Application/ApiClient.cs +++ b/src/GitHub.Api/Application/ApiClient.cs @@ -33,7 +33,7 @@ public ApiClient(UriString hostUrl, IKeychain keychain, IProcessManager processM this.taskManager = taskManager; this.nodeJsExecutablePath = nodeJsExecutablePath; this.octorunScriptPath = octorunScriptPath; - loginManager = new LoginManager(keychain, ApplicationInfo.ClientId, ApplicationInfo.ClientSecret, + loginManager = new LoginManager(keychain, processManager: processManager, taskManager: taskManager, nodeJsExecutablePath: nodeJsExecutablePath, diff --git a/src/GitHub.Api/Authentication/Credential.cs b/src/GitHub.Api/Authentication/Credential.cs index f7c74d3a5..2e31f9838 100644 --- a/src/GitHub.Api/Authentication/Credential.cs +++ b/src/GitHub.Api/Authentication/Credential.cs @@ -16,9 +16,10 @@ public Credential(UriString host, string username, string token) this.Token = token; } - public void UpdateToken(string token) + public void UpdateToken(string token, string username) { this.Token = token; + this.Username = username; } public UriString Host { get; private set; } diff --git a/src/GitHub.Api/Authentication/ICredentialManager.cs b/src/GitHub.Api/Authentication/ICredentialManager.cs index 28742a6c7..b601d3633 100644 --- a/src/GitHub.Api/Authentication/ICredentialManager.cs +++ b/src/GitHub.Api/Authentication/ICredentialManager.cs @@ -8,7 +8,7 @@ public interface ICredential : IDisposable UriString Host { get; } string Username { get; } string Token { get; } - void UpdateToken(string token); + void UpdateToken(string token, string username); } public interface ICredentialManager diff --git a/src/GitHub.Api/Authentication/IKeychain.cs b/src/GitHub.Api/Authentication/IKeychain.cs index 4aa1bcb97..a05a748dc 100644 --- a/src/GitHub.Api/Authentication/IKeychain.cs +++ b/src/GitHub.Api/Authentication/IKeychain.cs @@ -10,13 +10,12 @@ public interface IKeychain Task Load(UriString host); Task Clear(UriString host, bool deleteFromCredentialManager); Task Save(UriString host); - void UpdateToken(UriString host, string token); void SetCredentials(ICredential credential); void Initialize(); Connection[] Connections { get; } IList Hosts { get; } bool HasKeys { get; } - void SetToken(UriString host, string token); + void SetToken(UriString host, string token, string username); event Action ConnectionsChanged; } diff --git a/src/GitHub.Api/Authentication/Keychain.cs b/src/GitHub.Api/Authentication/Keychain.cs index 2b3c933e2..77289a5ba 100644 --- a/src/GitHub.Api/Authentication/Keychain.cs +++ b/src/GitHub.Api/Authentication/Keychain.cs @@ -95,11 +95,15 @@ public Keychain(IEnvironment environment, ICredentialManager credentialManager) public IKeychainAdapter Connect(UriString host) { + Guard.ArgumentNotNull(host, nameof(host)); + return FindOrCreateAdapter(host); } public async Task Load(UriString host) { + Guard.ArgumentNotNull(host, nameof(host)); + var keychainAdapter = FindOrCreateAdapter(host); var connection = GetConnection(host); @@ -145,6 +149,9 @@ public void Initialize() public async Task Clear(UriString host, bool deleteFromCredentialManager) { //logger.Trace("Clear Host:{0}", host); + + Guard.ArgumentNotNull(host, nameof(host)); + //clear octokit credentials await RemoveCredential(host, deleteFromCredentialManager); RemoveConnection(host); @@ -153,6 +160,9 @@ public async Task Clear(UriString host, bool deleteFromCredentialManager) public async Task Save(UriString host) { //logger.Trace("Save: {0}", host); + + Guard.ArgumentNotNull(host, nameof(host)); + var keychainAdapter = await AddCredential(host); AddConnection(new Connection(host, keychainAdapter.Credential.Username)); } @@ -160,23 +170,23 @@ public async Task Save(UriString host) public void SetCredentials(ICredential credential) { //logger.Trace("SetCredentials Host:{0}", credential.Host); + + Guard.ArgumentNotNull(credential, nameof(credential)); + var keychainAdapter = GetKeychainAdapter(credential.Host); keychainAdapter.Set(credential); } - public void SetToken(UriString host, string token) + public void SetToken(UriString host, string token, string username) { //logger.Trace("SetToken Host:{0}", host); - var keychainAdapter = GetKeychainAdapter(host); - keychainAdapter.UpdateToken(token); - } - public void UpdateToken(UriString host, string token) - { - //logger.Trace("UpdateToken Host:{0}", host); + Guard.ArgumentNotNull(host, nameof(host)); + Guard.ArgumentNotNull(token, nameof(token)); + Guard.ArgumentNotNull(username, nameof(username)); + var keychainAdapter = GetKeychainAdapter(host); - var keychainItem = keychainAdapter.Credential; - keychainItem.UpdateToken(token); + keychainAdapter.UpdateToken(token, username); } private void LoadConnectionsFromDisk() @@ -290,15 +300,6 @@ private void RemoveConnection(UriString host) } } - private void RemoveAllConnections() - { - if (connections.Count > 0) - { - connections.Clear(); - SaveConnectionsToDisk(); - } - } - private void UpdateConnections(Connection[] conns) { var updated = false; diff --git a/src/GitHub.Api/Authentication/KeychainAdapter.cs b/src/GitHub.Api/Authentication/KeychainAdapter.cs index 3fdde60b3..abbe9895e 100644 --- a/src/GitHub.Api/Authentication/KeychainAdapter.cs +++ b/src/GitHub.Api/Authentication/KeychainAdapter.cs @@ -9,9 +9,9 @@ public void Set(ICredential credential) Credential = credential; } - public void UpdateToken(string token) + public void UpdateToken(string token, string username) { - Credential.UpdateToken(token); + Credential.UpdateToken(token, username); } public void Clear() diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index 03c7d8bd3..0dfa49a9e 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Net; using System.Threading.Tasks; using GitHub.Logging; @@ -23,8 +21,6 @@ class LoginManager : ILoginManager private readonly ILogging logger = LogHelper.GetLogger(); private readonly IKeychain keychain; - private readonly string clientId; - private readonly string clientSecret; private readonly IProcessManager processManager; private readonly ITaskManager taskManager; private readonly NPath? nodeJsExecutablePath; @@ -34,25 +30,17 @@ class LoginManager : ILoginManager /// Initializes a new instance of the class. /// /// - /// The application's client API ID. - /// The application's client API secret. /// /// /// /// public LoginManager( - IKeychain keychain, - string clientId, - string clientSecret, - IProcessManager processManager = null, ITaskManager taskManager = null, NPath? nodeJsExecutablePath = null, NPath? octorunScript = null) + IKeychain keychain, IProcessManager processManager = null, ITaskManager taskManager = null, + NPath? nodeJsExecutablePath = null, NPath? octorunScript = null) { Guard.ArgumentNotNull(keychain, nameof(keychain)); - Guard.ArgumentNotNullOrWhiteSpace(clientId, nameof(clientId)); - Guard.ArgumentNotNullOrWhiteSpace(clientSecret, nameof(clientSecret)); this.keychain = keychain; - this.clientId = clientId; - this.clientSecret = clientSecret; this.processManager = processManager; this.taskManager = taskManager; this.nodeJsExecutablePath = nodeJsExecutablePath; @@ -84,7 +72,8 @@ public async Task Login( throw new InvalidOperationException("Returned token is null or empty"); } - keychain.SetToken(host, loginResultData.Token); + username = await RetrieveUsername(loginResultData, username); + keychain.SetToken(host, loginResultData.Token, username); if (loginResultData.Code == LoginResultCodes.Success) { @@ -123,7 +112,8 @@ public async Task ContinueLogin(LoginResultData loginResultData throw new InvalidOperationException("Returned token is null or empty"); } - keychain.SetToken(host, loginResultData.Token); + username = await RetrieveUsername(loginResultData, username); + keychain.SetToken(host, loginResultData.Token, username); await keychain.Save(host); return loginResultData; @@ -199,6 +189,25 @@ private async Task TryLogin( return new LoginResultData(LoginResultCodes.Failed, ret.GetApiErrorMessage() ?? "Failed.", host); } + + private async Task RetrieveUsername(LoginResultData loginResultData, string username) + { + if (!username.Contains("@")) + { + return username; + } + + var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath.Value, octorunScript.Value, "validate", + user: username, userToken: loginResultData.Token).Configure(processManager); + + var validateResult = await octorunTask.StartAsAsync(); + if (!validateResult.IsSuccess) + { + throw new InvalidOperationException("Authentication validation failed"); + } + + return validateResult.Output[1]; + } } class LoginResultData diff --git a/src/tests/UnitTests/Authentication/KeychainTests.cs b/src/tests/UnitTests/Authentication/KeychainTests.cs index ba5244abd..23786626b 100644 --- a/src/tests/UnitTests/Authentication/KeychainTests.cs +++ b/src/tests/UnitTests/Authentication/KeychainTests.cs @@ -292,7 +292,7 @@ public void ShouldConnectSetCredentialsTokenAndSave() keychainAdapter.Credential.Username.Should().Be(username); keychainAdapter.Credential.Token.Should().Be(password); - keychain.SetToken(hostUri, token); + keychain.SetToken(hostUri, token, username); keychainAdapter.Credential.Should().NotBeNull(); keychainAdapter.Credential.Host.Should().Be(hostUri);