From 15efd71b59ccfa64c34d5a284b518d6def38150c Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Mon, 23 Dec 2024 14:13:25 +0100 Subject: [PATCH 1/8] Part of SLVS-1732 Refactor ConnectionInformation --- .../UnintrusiveBindingControllerTests.cs | 4 +- .../Persistence/BasicAuthCredentialsTests.cs | 72 +++++++++++++++++++ .../BoundSonarQubeProjectExtensionsTests.cs | 25 +++---- .../Binding/IUnintrusiveBindingController.cs | 3 +- .../Persistence/BasicAuthCredentials.cs | 24 +++---- .../Persistence/ConnectionInfoConverter.cs | 38 ---------- .../BoundSonarQubeProjectExtensions.cs | 8 +-- src/Core/Binding/ICredentials.cs | 4 +- .../Service/ConnectionInformationTests.cs | 24 +++---- .../CallRealServerTestHarness.cs | 17 +++-- .../AuthenticationHeaderFactoryTests.cs | 58 +++++++++++++-- .../Models/ConnectionInformationTests.cs | 53 ++++++++------ .../SonarQubeService_Lifecycle.cs | 17 ++--- .../SonarQubeService_TestBase.cs | 16 +++-- .../Helpers/AuthenticationHeaderFactory.cs | 24 ++++--- .../Models/ConnectionInformation.cs | 20 ++---- .../Models/IConnectionCredentials.cs | 37 ++++++++++ ...AuthenticationType.cs => NoCredentials.cs} | 8 ++- src/SonarQube.Client/SonarQubeService.cs | 3 +- 19 files changed, 288 insertions(+), 167 deletions(-) create mode 100644 src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs delete mode 100644 src/ConnectedMode/Persistence/ConnectionInfoConverter.cs create mode 100644 src/SonarQube.Client/Models/IConnectionCredentials.cs rename src/SonarQube.Client/Models/{AuthenticationType.cs => NoCredentials.cs} (83%) diff --git a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs index 2192a3228c..7d30e3699a 100644 --- a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs @@ -86,8 +86,8 @@ await sonarQubeService .Received() .ConnectAsync( Arg.Is(x => x.ServerUri.Equals("https://sonarcloud.io/") - && x.UserName.Equals(ValidToken.UserName) - && string.IsNullOrEmpty(x.Password.ToUnsecureString())), + && ((BasicAuthCredentials)x.Credentials).UserName.Equals(ValidToken.UserName) + && string.IsNullOrEmpty(((BasicAuthCredentials)x.Credentials).Password.ToUnsecureString())), ACancellationToken); } diff --git a/src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs b/src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs new file mode 100644 index 0000000000..01fc2d12ea --- /dev/null +++ b/src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs @@ -0,0 +1,72 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.TestInfrastructure; +using SonarQube.Client.Helpers; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; + +[TestClass] +public class BasicAuthCredentialsTests +{ + private const string Username = "username"; + private const string Password = "pwd"; + + [TestMethod] + public void Ctor_WhenUsernameIsNull_ThrowsArgumentNullException() + { + Action act = () => new BasicAuthCredentials(null, Password.ToSecureString()); + + act.Should().Throw(); + } + + [TestMethod] + public void Ctor_WhenPasswordIsNull_ThrowsArgumentNullException() + { + Action act = () => new BasicAuthCredentials(Username, null); + + act.Should().Throw(); + } + + [TestMethod] + public void Dispose_DisposesPassword() + { + var testSubject = new BasicAuthCredentials(Username, Password.ToSecureString()); + + testSubject.Dispose(); + + Exceptions.Expect(() => testSubject.Password.ToUnsecureString()); + } + + [TestMethod] + public void Clone_ClonesPassword() + { + var password = "pwd"; + var testSubject = new BasicAuthCredentials(Username, password.ToSecureString()); + + var clone = (BasicAuthCredentials)testSubject.Clone(); + + clone.Should().NotBeSameAs(testSubject); + clone.Password.Should().NotBeSameAs(testSubject.Password); + clone.Password.ToUnsecureString().Should().Be(testSubject.Password.ToUnsecureString()); + clone.UserName.Should().Be(testSubject.UserName); + } +} diff --git a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs index 00dcacdc8c..3a38310583 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using Microsoft.VisualStudio.LanguageServices.Progression; using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.TestInfrastructure; @@ -47,8 +48,7 @@ public void BoundSonarQubeProject_CreateConnectionInformation_NoCredentials() // Assert conn.ServerUri.Should().Be(input.ServerUri); - conn.UserName.Should().BeNull(); - conn.Password.Should().BeNull(); + conn.Credentials.Should().BeAssignableTo(); conn.Organization.Key.Should().Be("org_key"); conn.Organization.Name.Should().Be("org_name"); } @@ -66,8 +66,10 @@ public void BoundSonarQubeProject_CreateConnectionInformation_BasicAuthCredentia // Assert conn.ServerUri.Should().Be(input.ServerUri); - conn.UserName.Should().Be(creds.UserName); - conn.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); + var basicAuth = conn.Credentials as BasicAuthCredentials; + basicAuth.Should().NotBeNull(); + basicAuth.UserName.Should().Be(creds.UserName); + basicAuth.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); conn.Organization.Key.Should().Be("org_key"); conn.Organization.Name.Should().Be("org_name"); } @@ -83,8 +85,7 @@ public void BoundSonarQubeProject_CreateConnectionInformation_NoOrganizationNoAu // Assert conn.ServerUri.Should().Be(input.ServerUri); - conn.UserName.Should().BeNull(); - conn.Password.Should().BeNull(); + conn.Credentials.Should().BeAssignableTo(); conn.Organization.Should().BeNull(); } @@ -105,8 +106,7 @@ public void BoundServerProject_CreateConnectionInformation_NoCredentials() // Assert conn.ServerUri.Should().Be(input.ServerConnection.ServerUri); - conn.UserName.Should().BeNull(); - conn.Password.Should().BeNull(); + conn.Credentials.Should().BeAssignableTo(); conn.Organization.Key.Should().Be("org_key"); } @@ -123,8 +123,10 @@ public void BoundServerProject_CreateConnectionInformation_BasicAuthCredentials( // Assert conn.ServerUri.Should().Be(input.ServerConnection.ServerUri); - conn.UserName.Should().Be(creds.UserName); - conn.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); + var basicAuth = conn.Credentials as BasicAuthCredentials; + basicAuth.Should().NotBeNull(); + basicAuth.UserName.Should().Be(creds.UserName); + basicAuth.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); conn.Organization.Key.Should().Be("org_key"); } @@ -139,8 +141,7 @@ public void BoundServerProject_CreateConnectionInformation_NoOrganizationNoAuth( // Assert conn.ServerUri.Should().Be(input.ServerConnection.ServerUri); - conn.UserName.Should().BeNull(); - conn.Password.Should().BeNull(); + conn.Credentials.Should().BeAssignableTo(); conn.Organization.Should().BeNull(); } } diff --git a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs index 96d2432899..f4df181622 100644 --- a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs +++ b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs @@ -21,6 +21,7 @@ using System.ComponentModel.Composition; using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client; +using SonarQube.Client.Models; using Task = System.Threading.Tasks.Task; namespace SonarLint.VisualStudio.ConnectedMode.Binding @@ -57,7 +58,7 @@ public UnintrusiveBindingController(IBindingProcessFactory bindingProcessFactory public async Task BindAsync(BoundServerProject project, CancellationToken cancellationToken) { - var connectionInformation = project.ServerConnection.Credentials.CreateConnectionInformation(project.ServerConnection.ServerUri); + var connectionInformation = new ConnectionInformation(project.ServerConnection.ServerUri, project.ServerConnection.Credentials); await sonarQubeService.ConnectAsync(connectionInformation, cancellationToken); await BindAsync(project, null, cancellationToken); activeSolutionChangedHandler.HandleBindingChange(false); diff --git a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs b/src/ConnectedMode/Persistence/BasicAuthCredentials.cs index d13d6255dc..927af48a6a 100644 --- a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs +++ b/src/ConnectedMode/Persistence/BasicAuthCredentials.cs @@ -18,28 +18,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Security; using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Helpers; using SonarQube.Client.Models; -namespace SonarLint.VisualStudio.ConnectedMode.Persistence +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +internal sealed class BasicAuthCredentials(string userName, SecureString password) : ICredentials, IBasicAuthCredentials { - internal class BasicAuthCredentials : ICredentials - { - public BasicAuthCredentials(string userName, SecureString password) - { - this.UserName = userName; - this.Password = password; - } + public string UserName { get; } = userName ?? throw new ArgumentNullException(nameof(userName)); - public string UserName { get; } + public SecureString Password { get; } = password ?? throw new ArgumentNullException(nameof(password)); - public SecureString Password { get; } + public void Dispose() => Password?.Dispose(); - ConnectionInformation ICredentials.CreateConnectionInformation(Uri serverUri) - { - return new ConnectionInformation(serverUri, this.UserName, this.Password); - } - } + public object Clone() => new BasicAuthCredentials(UserName, Password.CopyAsReadOnly()); } diff --git a/src/ConnectedMode/Persistence/ConnectionInfoConverter.cs b/src/ConnectedMode/Persistence/ConnectionInfoConverter.cs deleted file mode 100644 index 8162240004..0000000000 --- a/src/ConnectedMode/Persistence/ConnectionInfoConverter.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System.Diagnostics.CodeAnalysis; -using SonarLint.VisualStudio.Core.Binding; -using SonarQube.Client.Models; - -namespace SonarLint.VisualStudio.ConnectedMode.Persistence; - -[ExcludeFromCodeCoverage] // todo remove https://sonarsource.atlassian.net/browse/SLVS-1408 -public static class ConnectionInfoConverter -{ - public static ServerConnection ToServerConnection(this ConnectionInformation connectionInformation) => - connectionInformation switch - { - { Organization.Key: { } organization } => new ServerConnection.SonarCloud(organization, - credentials: new BasicAuthCredentials(connectionInformation.UserName, connectionInformation.Password)), - _ => new ServerConnection.SonarQube(connectionInformation.ServerUri, - credentials: new BasicAuthCredentials(connectionInformation.UserName, connectionInformation.Password)) - }; -} diff --git a/src/Core/Binding/BoundSonarQubeProjectExtensions.cs b/src/Core/Binding/BoundSonarQubeProjectExtensions.cs index abbcc63204..af90addb72 100644 --- a/src/Core/Binding/BoundSonarQubeProjectExtensions.cs +++ b/src/Core/Binding/BoundSonarQubeProjectExtensions.cs @@ -32,9 +32,7 @@ public static ConnectionInformation CreateConnectionInformation(this BoundSonarQ throw new ArgumentNullException(nameof(binding)); } - var connection = binding.Credentials == null ? - new ConnectionInformation(binding.ServerUri) - : binding.Credentials.CreateConnectionInformation(binding.ServerUri); + var connection = new ConnectionInformation(binding.ServerUri, binding.Credentials); connection.Organization = binding.Organization; return connection; @@ -47,9 +45,7 @@ public static ConnectionInformation CreateConnectionInformation(this BoundServer throw new ArgumentNullException(nameof(binding)); } - var connection = binding.ServerConnection.Credentials == null ? - new ConnectionInformation(binding.ServerConnection.ServerUri) - : binding.ServerConnection.Credentials.CreateConnectionInformation(binding.ServerConnection.ServerUri); + var connection = new ConnectionInformation(binding.ServerConnection.ServerUri, binding.ServerConnection.Credentials); connection.Organization = binding.ServerConnection is ServerConnection.SonarCloud sc ? new SonarQubeOrganization(sc.OrganizationKey, null) : null; return connection; diff --git a/src/Core/Binding/ICredentials.cs b/src/Core/Binding/ICredentials.cs index 4a488970cd..d25430a114 100644 --- a/src/Core/Binding/ICredentials.cs +++ b/src/Core/Binding/ICredentials.cs @@ -18,13 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using SonarQube.Client.Models; namespace SonarLint.VisualStudio.Core.Binding { - public interface ICredentials + public interface ICredentials : IConnectionCredentials { - ConnectionInformation CreateConnectionInformation(Uri serverUri); } } diff --git a/src/Integration.UnitTests/Service/ConnectionInformationTests.cs b/src/Integration.UnitTests/Service/ConnectionInformationTests.cs index c4ccb8625f..c03032f070 100644 --- a/src/Integration.UnitTests/Service/ConnectionInformationTests.cs +++ b/src/Integration.UnitTests/Service/ConnectionInformationTests.cs @@ -21,6 +21,7 @@ using System; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarQube.Client.Helpers; using SonarQube.Client.Models; using SonarLint.VisualStudio.TestInfrastructure; @@ -38,14 +39,15 @@ public void ConnectionInformation_WithLoginInformation() var passwordUnsecure = "admin"; var password = passwordUnsecure.ToSecureString(); var serverUri = new Uri("http://localhost/"); - var testSubject = new ConnectionInformation(serverUri, userName, password); + var credentials = new BasicAuthCredentials(userName, password); + var testSubject = new ConnectionInformation(serverUri, credentials); // Act password.Dispose(); // Connection information should maintain it's own copy of the password // Assert - testSubject.Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); - testSubject.UserName.Should().Be(userName, "UserName doesn't match"); + ((BasicAuthCredentials)testSubject.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); + ((BasicAuthCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); testSubject.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); // Act clone @@ -55,11 +57,11 @@ public void ConnectionInformation_WithLoginInformation() testSubject.Dispose(); // Assert testSubject - Exceptions.Expect(() => testSubject.Password.ToUnsecureString()); + Exceptions.Expect(() => ((BasicAuthCredentials)testSubject.Credentials).Password.ToUnsecureString()); // Assert testSubject2 - testSubject2.Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); - testSubject2.UserName.Should().Be(userName, "UserName doesn't match"); + ((BasicAuthCredentials)testSubject2.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); + ((BasicAuthCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); testSubject2.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); } @@ -70,19 +72,17 @@ public void ConnectionInformation_WithoutLoginInformation() var serverUri = new Uri("http://localhost/"); // Act - var testSubject = new ConnectionInformation(serverUri); + var testSubject = new ConnectionInformation(serverUri, null); // Assert - testSubject.Password.Should().BeNull("Password wasn't provided"); - testSubject.UserName.Should().BeNull("UserName wasn't provided"); + testSubject.Credentials.Should().BeAssignableTo(); testSubject.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); // Act clone var testSubject2 = (ConnectionInformation)((ICloneable)testSubject).Clone(); // Assert testSubject2 - testSubject2.Password.Should().BeNull("Password wasn't provided"); - testSubject2.UserName.Should().BeNull("UserName wasn't provided"); + testSubject2.Credentials.Should().BeAssignableTo(); testSubject2.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); } @@ -110,7 +110,7 @@ public void ConnectionInformation_Ctor_FixesSonarCloudUri() public void ConnectionInformation_Ctor_ArgChecks() { Exceptions.Expect(() => new ConnectionInformation(null)); - Exceptions.Expect(() => new ConnectionInformation(null, "user", "pwd".ToSecureString())); + Exceptions.Expect(() => new ConnectionInformation(null, new BasicAuthCredentials("user", "pwd".ToSecureString()))); } } } diff --git a/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs b/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs index a3e6c116a5..d4bc0f4f04 100644 --- a/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs +++ b/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs @@ -18,13 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Net.Http; using System.Security; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SonarQube.Client.Models; using SonarQube.Client.Tests.Infra; @@ -53,7 +48,7 @@ public async Task Call_Real_SonarQube() password.AppendChar('i'); password.AppendChar('n'); - var connInfo = new ConnectionInformation(url, userName, password); + var connInfo = new ConnectionInformation(url, MockBasicAuthCredentials(userName, password)); var service = new SonarQubeService(new HttpClientHandler(), "agent", new TestLogger()); try @@ -79,7 +74,7 @@ public async Task Call_Real_SonarCloud() var url = ConnectionInformation.FixedSonarCloudUri; var password = new SecureString(); - var connInfo = new ConnectionInformation(url, validSonarCloudToken, password); + var connInfo = new ConnectionInformation(url, MockBasicAuthCredentials(validSonarCloudToken, password)); var service = new SonarQubeService(new HttpClientHandler(), "agent", new TestLogger()); try @@ -96,5 +91,13 @@ public async Task Call_Real_SonarCloud() service.Disconnect(); } } + + private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + { + var mock = Substitute.For(); + mock.UserName.Returns(userName); + mock.Password.Returns(password); + return mock; + } } } diff --git a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs index 723c6d23af..81a2919777 100644 --- a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs +++ b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs @@ -18,18 +18,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Security; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarQube.Client.Tests.Helpers { [TestClass] public class AuthenticationHeaderFactoryTests { + private const string Password = "password"; + private const string Username = "username"; + [TestMethod] - public void AuthenticationHeaderHelper_GetAuthToken() + public void GetAuthToken_ReturnsExpectedString() { // Invalid input string user = "hello:"; @@ -59,8 +61,48 @@ public void AuthenticationHeaderHelper_GetAuthToken() AuthenticationHeaderFactory.GetBasicAuthToken(user, password.ToSecureString())); } + [TestMethod] + public void Create_NoCredentials_ReturnsNull() + { + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(new NoCredentials()); + + authenticationHeaderValue.Should().BeNull(); + } + + [TestMethod] + public void Create_UnsupportedAuthentication_ReturnsNull() + { + using var scope = new AssertIgnoreScope(); + + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(null); + + authenticationHeaderValue.Should().BeNull(); + } + + [TestMethod] + public void Create_BasicAuth_UsernameIsNull_Throws() + { + var credentials = MockBasicAuthCredentials(null, Password.ToSecureString()); + + var act = () => AuthenticationHeaderFactory.Create(credentials); + + act.Should().Throw(); + } + + [TestMethod] + public void Create_BasicAuth_CredentialsProvided_ReturnsBasicScheme() + { + var credentials = MockBasicAuthCredentials(Username, Password.ToSecureString()); + + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(credentials); + + authenticationHeaderValue.Scheme.Should().Be("Basic"); + AssertAreEqualUserNameAndPassword(Username, Password, authenticationHeaderValue.Parameter); + } + private void AssertAreEqualUserNameAndPassword(string expectedUser, string expectedPassword, string userAndPasswordBase64String) + { string userNameAndPassword = AuthenticationHeaderFactory.BasicAuthEncoding.GetString(Convert.FromBase64String(userAndPasswordBase64String)); @@ -76,5 +118,13 @@ private void AssertAreEqualUserNameAndPassword(string expectedUser, string expec userNameAndPasswordTokens.Should().HaveElementAt(0, expectedUser); userNameAndPasswordTokens.Should().HaveElementAt(1, expectedPassword); } + + private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + { + var mock = Substitute.For(); + mock.UserName.Returns(userName); + mock.Password.Returns(password); + return mock; + } } } diff --git a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs index 07df37d347..7d5e4d36ed 100644 --- a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs +++ b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs @@ -18,10 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Security; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SonarQube.Client.Helpers; using SonarQube.Client.Models; @@ -54,14 +51,14 @@ public void Ctor_SonarQubeUrl_IsProcessedCorrectly(string inputUrl, string expec } [TestMethod] - [DataRow("http://sonarcloud.io") ] - [DataRow("http://sonarcloud.io/") ] - [DataRow("https://sonarcloud.io") ] - [DataRow("https://sonarcloud.io/") ] - [DataRow("http://SONARCLOUD.IO") ] - [DataRow("http://www.sonarcloud.io") ] - [DataRow("https://www.sonarcloud.io/") ] - [DataRow("http://sonarcloud.io:9999") ] + [DataRow("http://sonarcloud.io")] + [DataRow("http://sonarcloud.io/")] + [DataRow("https://sonarcloud.io")] + [DataRow("https://sonarcloud.io/")] + [DataRow("http://SONARCLOUD.IO")] + [DataRow("http://www.sonarcloud.io")] + [DataRow("https://www.sonarcloud.io/")] + [DataRow("http://sonarcloud.io:9999")] public void Ctor_SonarCloudUrl_IsProcessedCorrectly(string inputUrl) { var testSubject = new ConnectionInformation(new Uri(inputUrl)); @@ -80,8 +77,9 @@ public void Clone_PropertiesAreCopiedCorrectly(string serverUrl, string userName { var securePwd = InitializeSecureString(password); var org = InitializeOrganization(orgKey); + var credentials = MockBasicAuthCredentials(userName, securePwd); - var testSubject = new ConnectionInformation(new Uri(serverUrl), userName, securePwd) + var testSubject = new ConnectionInformation(new Uri(serverUrl), credentials) { Organization = org }; @@ -90,20 +88,20 @@ public void Clone_PropertiesAreCopiedCorrectly(string serverUrl, string userName cloneObj.Should().BeOfType(); CheckPropertiesMatch(testSubject, (ConnectionInformation)cloneObj); + _= credentials.Received().Clone(); } [TestMethod] public void Dispose_PasswordIsDisposed() { var pwd = "secret".ToSecureString(); - var testSubject = new ConnectionInformation(new Uri("http://any"), "any", pwd); + var credentials = MockBasicAuthCredentials("any", pwd); + var testSubject = new ConnectionInformation(new Uri("http://any"), credentials); testSubject.Dispose(); testSubject.IsDisposed.Should().BeTrue(); - - Action accessPassword = () => _ = testSubject.Password.Length; - accessPassword.Should().ThrowExactly(); + credentials.Received(1).Dispose(); } private static SecureString InitializeSecureString(string password) => @@ -116,21 +114,32 @@ private static SonarQubeOrganization InitializeOrganization(string orgKey) => private static void CheckPropertiesMatch(ConnectionInformation item1, ConnectionInformation item2) { item1.ServerUri.Should().Be(item2.ServerUri); - item1.UserName.Should().Be(item2.UserName); - item1.Organization.Should().Be(item2.Organization); + var credentials1 = (IBasicAuthCredentials)item1.Credentials; + var credentials2 = (IBasicAuthCredentials)item2.Credentials; + + credentials1.UserName.Should().Be(credentials2.UserName); + item1.Organization.Should().Be(item2.Organization); - if (item1.Password == null) + if (credentials1.Password == null) { - item2.Password.Should().BeNull(); + credentials2.Password.Should().BeNull(); } else { - item1.Password.ToUnsecureString().Should().Be(item2.Password.ToUnsecureString()); + credentials1.Password.ToUnsecureString().Should().Be(credentials2.Password.ToUnsecureString()); } - item1.Authentication.Should().Be(item2.Authentication); item1.IsSonarCloud.Should().Be(item2.IsSonarCloud); } + + private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + { + var mock = Substitute.For(); + mock.UserName.Returns(userName); + mock.Password.Returns(password); + mock.Clone().Returns(mock); + return mock; + } } } diff --git a/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs b/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs index 187663f918..26687598d6 100644 --- a/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs +++ b/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs @@ -18,13 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Moq.Protected; -using SonarQube.Client.Helpers; using SonarQube.Client.Models; namespace SonarQube.Client.Tests @@ -43,7 +38,7 @@ public async Task Connect_To_SonarQube_Valid_Credentials() service.GetServerInfo().Should().BeNull(); await service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), "user", "pass".ToSecureString()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); service.IsConnected.Should().BeTrue(); @@ -61,7 +56,7 @@ public async Task Connect_To_SonarQube_Invalid_Credentials() service.GetServerInfo().Should().BeNull(); Func act = () => service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), "user", "pass".ToSecureString()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); var ex = await act.Should().ThrowAsync(); @@ -83,7 +78,7 @@ public async Task Connect_ServerIsNotReachable_IsConnectedIsFalse() service.GetServerInfo().Should().BeNull(); Func act = () => service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), "user", "pass".ToSecureString()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); var ex = await act.Should().ThrowAsync(); @@ -106,7 +101,7 @@ public async Task Connect_SonarQube_IsSonarCloud_SonarQubeUrl_ReturnsFalse(strin SetupRequest("api/authentication/validate", "{ \"valid\": true }", serverUrl: canonicalUrl); await service.ConnectAsync( - new ConnectionInformation(new Uri(inputUrl), "user", "pass".ToSecureString()), + new ConnectionInformation(new Uri(inputUrl), Mock.Of()), CancellationToken.None); service.GetServerInfo().ServerType.Should().Be(ServerType.SonarQube); @@ -126,7 +121,7 @@ public async Task Connect_SonarQube_IsSonarCloud_SonarCloud_ReturnTrue(string in SetupRequest("api/authentication/validate", "{ \"valid\": true }", serverUrl: fixedSonarCloudUrl); await service.ConnectAsync( - new ConnectionInformation(new Uri(inputUrl), "user", "pass".ToSecureString()), + new ConnectionInformation(new Uri(inputUrl), Mock.Of()), CancellationToken.None); service.GetServerInfo().ServerType.Should().Be(ServerType.SonarCloud); diff --git a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs index d23b649a73..bf63208f8e 100644 --- a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs +++ b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs @@ -18,16 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Globalization; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; using SonarQube.Client.Helpers; @@ -94,7 +89,7 @@ protected async Task ConnectToSonarQube(string version = "5.6.0.0", string serve SetupRequest("api/authentication/validate", "{ \"valid\": true}", serverUrl: serverUrl); await service.ConnectAsync( - new ConnectionInformation(new Uri(serverUrl), "valeri", new SecureString()), + new ConnectionInformation(new Uri(serverUrl), MockBasicAuthCredentials("valeri", new SecureString())), CancellationToken.None); // Sanity checks @@ -120,5 +115,14 @@ protected internal virtual SonarQubeService CreateTestSubject() { return new SonarQubeService(messageHandler.Object, UserAgent, logger, requestFactorySelector, secondaryIssueHashUpdater.Object, sseStreamFactory.Object); } + + private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + { + var mock = new Mock(); + mock.SetupGet(x => x.UserName).Returns(userName); + mock.SetupGet(x => x.Password).Returns(password); + + return mock.Object; + } } } diff --git a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs index 5f92668a9a..f5637c728a 100644 --- a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs +++ b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Diagnostics; using System.Net.Http.Headers; using System.Security; using System.Text; @@ -36,20 +34,20 @@ public static class AuthenticationHeaderFactory /// internal static readonly Encoding BasicAuthEncoding = Encoding.UTF8; - public static AuthenticationHeaderValue Create(string userName, SecureString password, AuthenticationType authentication) + public static AuthenticationHeaderValue Create(IConnectionCredentials credentials) { - if (authentication == AuthenticationType.Basic) + if (credentials is IBasicAuthCredentials basicAuthCredentials) { - return string.IsNullOrWhiteSpace(userName) - ? null - : new AuthenticationHeaderValue("Basic", GetBasicAuthToken(userName, password)); + ValidateCredentials(basicAuthCredentials); + return new AuthenticationHeaderValue("Basic", GetBasicAuthToken(basicAuthCredentials.UserName, basicAuthCredentials.Password)); // See more info: https://www.visualstudio.com/en-us/integrate/get-started/auth/overview } - else + if (credentials is INoCredentials) { - Debug.Fail("Unsupported Authentication: " + authentication); return null; } + Debug.Fail("Unsupported Authentication: " + credentials?.GetType()); + return null; } internal static string GetBasicAuthToken(string user, SecureString password) @@ -64,5 +62,13 @@ internal static string GetBasicAuthToken(string user, SecureString password) return Convert.ToBase64String(BasicAuthEncoding.GetBytes(string.Join(BasicAuthCredentialSeparator, user, password.ToUnsecureString()))); } + + private static void ValidateCredentials(IBasicAuthCredentials basicAuthCredentials) + { + if (string.IsNullOrEmpty(basicAuthCredentials.UserName)) + { + throw new ArgumentException(nameof(basicAuthCredentials.UserName)); + } + } } } diff --git a/src/SonarQube.Client/Models/ConnectionInformation.cs b/src/SonarQube.Client/Models/ConnectionInformation.cs index 9eb320a09c..2dafca49e8 100644 --- a/src/SonarQube.Client/Models/ConnectionInformation.cs +++ b/src/SonarQube.Client/Models/ConnectionInformation.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Security; using SonarQube.Client.Helpers; namespace SonarQube.Client.Models @@ -35,7 +33,7 @@ public sealed class ConnectionInformation : ICloneable, IDisposable private bool isDisposed; - public ConnectionInformation(Uri serverUri, string userName, SecureString password) + public ConnectionInformation(Uri serverUri, IConnectionCredentials credentials) { if (serverUri == null) { @@ -43,14 +41,12 @@ public ConnectionInformation(Uri serverUri, string userName, SecureString passwo } ServerUri = FixSonarCloudUri(serverUri).EnsureTrailingSlash(); - UserName = userName; - Password = password?.CopyAsReadOnly(); - Authentication = AuthenticationType.Basic; // Only one supported at this point + Credentials = (IConnectionCredentials)credentials?.Clone() ?? new NoCredentials(); IsSonarCloud = ServerUri == FixedSonarCloudUri; } public ConnectionInformation(Uri serverUri) - : this(serverUri, null, null) + : this(serverUri, null) { } @@ -58,11 +54,7 @@ public ConnectionInformation(Uri serverUri) public bool IsSonarCloud { get; } - public string UserName { get; } - - public SecureString Password { get; } - - public AuthenticationType Authentication { get; } + public IConnectionCredentials Credentials { get; } public bool IsDisposed => isDisposed; @@ -70,7 +62,7 @@ public ConnectionInformation(Uri serverUri) public ConnectionInformation Clone() { - return new ConnectionInformation(ServerUri, UserName, Password?.CopyAsReadOnly()) { Organization = Organization }; + return new ConnectionInformation(ServerUri, (IConnectionCredentials)Credentials?.Clone()) { Organization = Organization }; } object ICloneable.Clone() @@ -96,7 +88,7 @@ public void Dispose() { if (!isDisposed) { - Password?.Dispose(); + Credentials?.Dispose(); isDisposed = true; } } diff --git a/src/SonarQube.Client/Models/IConnectionCredentials.cs b/src/SonarQube.Client/Models/IConnectionCredentials.cs new file mode 100644 index 0000000000..88cbe512ad --- /dev/null +++ b/src/SonarQube.Client/Models/IConnectionCredentials.cs @@ -0,0 +1,37 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Security; + +namespace SonarQube.Client.Models; + +public interface IConnectionCredentials : IDisposable, ICloneable +{ +} + +public interface IBasicAuthCredentials : IConnectionCredentials +{ + public string UserName { get; } + public SecureString Password { get; } +} + +public interface INoCredentials : IConnectionCredentials +{ +} diff --git a/src/SonarQube.Client/Models/AuthenticationType.cs b/src/SonarQube.Client/Models/NoCredentials.cs similarity index 83% rename from src/SonarQube.Client/Models/AuthenticationType.cs rename to src/SonarQube.Client/Models/NoCredentials.cs index acc6c4a0a3..fdae5285d4 100644 --- a/src/SonarQube.Client/Models/AuthenticationType.cs +++ b/src/SonarQube.Client/Models/NoCredentials.cs @@ -18,7 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarQube.Client.Models +namespace SonarQube.Client.Models; + +internal sealed class NoCredentials : INoCredentials { - public enum AuthenticationType { Basic } + public void Dispose() { } + + public object Clone() => new NoCredentials(); } diff --git a/src/SonarQube.Client/SonarQubeService.cs b/src/SonarQube.Client/SonarQubeService.cs index 35617bf6fd..63a3f72764 100644 --- a/src/SonarQube.Client/SonarQubeService.cs +++ b/src/SonarQube.Client/SonarQubeService.cs @@ -144,8 +144,7 @@ public async Task ConnectAsync(ConnectionInformation connection, CancellationTok BaseAddress = connection.ServerUri, DefaultRequestHeaders = { - Authorization = AuthenticationHeaderFactory.Create( - connection.UserName, connection.Password, connection.Authentication), + Authorization = AuthenticationHeaderFactory.Create(connection.Credentials), }, }; From f45e20bf36b5dd984c8460ec464edf41c79153b8 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Mon, 23 Dec 2024 17:12:16 +0100 Subject: [PATCH 2/8] CaYC: cleanup code applied in touched files --- .../BoundSonarQubeProjectExtensionsTests.cs | 3 +- .../Binding/IUnintrusiveBindingController.cs | 9 +- .../BoundSonarQubeProjectExtensions.cs | 2 +- .../AuthenticationHeaderFactoryTests.cs | 4 +- .../Models/ConnectionInformationTests.cs | 16 ++-- .../SonarQubeService_TestBase.cs | 6 +- src/SonarQube.Client/SonarQubeService.cs | 85 ++++++++++++------- 7 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs index 3a38310583..dfcd94f9a0 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs @@ -88,7 +88,7 @@ public void BoundSonarQubeProject_CreateConnectionInformation_NoOrganizationNoAu conn.Credentials.Should().BeAssignableTo(); conn.Organization.Should().BeNull(); } - + [TestMethod] public void BoundServerProject_CreateConnectionInformation_ArgCheck() { @@ -109,7 +109,6 @@ public void BoundServerProject_CreateConnectionInformation_NoCredentials() conn.Credentials.Should().BeAssignableTo(); conn.Organization.Key.Should().Be("org_key"); } - [TestMethod] public void BoundServerProject_CreateConnectionInformation_BasicAuthCredentials() diff --git a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs index f4df181622..86330146bf 100644 --- a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs +++ b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs @@ -29,9 +29,10 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding public interface IBindingController { Task BindAsync(BoundServerProject project, CancellationToken cancellationToken); + bool Unbind(string localBindingKey); } - + internal interface IUnintrusiveBindingController { Task BindAsync(BoundServerProject project, IProgress progress, CancellationToken token); @@ -48,7 +49,11 @@ internal class UnintrusiveBindingController : IUnintrusiveBindingController, IBi private readonly ISolutionBindingRepository solutionBindingRepository; [ImportingConstructor] - public UnintrusiveBindingController(IBindingProcessFactory bindingProcessFactory, ISonarQubeService sonarQubeService, IActiveSolutionChangedHandler activeSolutionChangedHandler, ISolutionBindingRepository solutionBindingRepository) + public UnintrusiveBindingController( + IBindingProcessFactory bindingProcessFactory, + ISonarQubeService sonarQubeService, + IActiveSolutionChangedHandler activeSolutionChangedHandler, + ISolutionBindingRepository solutionBindingRepository) { this.bindingProcessFactory = bindingProcessFactory; this.sonarQubeService = sonarQubeService; diff --git a/src/Core/Binding/BoundSonarQubeProjectExtensions.cs b/src/Core/Binding/BoundSonarQubeProjectExtensions.cs index af90addb72..29af790919 100644 --- a/src/Core/Binding/BoundSonarQubeProjectExtensions.cs +++ b/src/Core/Binding/BoundSonarQubeProjectExtensions.cs @@ -37,7 +37,7 @@ public static ConnectionInformation CreateConnectionInformation(this BoundSonarQ connection.Organization = binding.Organization; return connection; } - + public static ConnectionInformation CreateConnectionInformation(this BoundServerProject binding) { if (binding == null) diff --git a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs index 81a2919777..9653ae7d77 100644 --- a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs +++ b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs @@ -100,7 +100,9 @@ public void Create_BasicAuth_CredentialsProvided_ReturnsBasicScheme() AssertAreEqualUserNameAndPassword(Username, Password, authenticationHeaderValue.Parameter); } - private void AssertAreEqualUserNameAndPassword(string expectedUser, string expectedPassword, + private void AssertAreEqualUserNameAndPassword( + string expectedUser, + string expectedPassword, string userAndPasswordBase64String) { diff --git a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs index 7d5e4d36ed..6f4959a007 100644 --- a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs +++ b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs @@ -73,22 +73,23 @@ public void Ctor_SonarCloudUrl_IsProcessedCorrectly(string inputUrl) [DataRow("http://localhost", "user1", "secret", null)] [DataRow("http://sonarcloud.io", null, null, "myorg")] [DataRow("http://sonarcloud.io", "a token", null, "myorg")] - public void Clone_PropertiesAreCopiedCorrectly(string serverUrl, string userName, string password, string orgKey) + public void Clone_PropertiesAreCopiedCorrectly( + string serverUrl, + string userName, + string password, + string orgKey) { var securePwd = InitializeSecureString(password); var org = InitializeOrganization(orgKey); var credentials = MockBasicAuthCredentials(userName, securePwd); - var testSubject = new ConnectionInformation(new Uri(serverUrl), credentials) - { - Organization = org - }; + var testSubject = new ConnectionInformation(new Uri(serverUrl), credentials) { Organization = org }; var cloneObj = ((ICloneable)testSubject).Clone(); cloneObj.Should().BeOfType(); CheckPropertiesMatch(testSubject, (ConnectionInformation)cloneObj); - _= credentials.Received().Clone(); + _ = credentials.Received().Clone(); } [TestMethod] @@ -108,8 +109,7 @@ private static SecureString InitializeSecureString(string password) => // The "ToSecureString" doesn't expect nulls, which we want to use in the tests password?.ToSecureString(); - private static SonarQubeOrganization InitializeOrganization(string orgKey) => - orgKey == null ? null : new SonarQubeOrganization(orgKey, Guid.NewGuid().ToString()); + private static SonarQubeOrganization InitializeOrganization(string orgKey) => orgKey == null ? null : new SonarQubeOrganization(orgKey, Guid.NewGuid().ToString()); private static void CheckPropertiesMatch(ConnectionInformation item1, ConnectionInformation item2) { diff --git a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs index bf63208f8e..72a5e02308 100644 --- a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs +++ b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs @@ -66,7 +66,11 @@ public void TestInitialize() ResetService(); } - protected void SetupRequest(string relativePath, string response, HttpStatusCode statusCode = HttpStatusCode.OK, string serverUrl = DefaultBasePath) => + protected void SetupRequest( + string relativePath, + string response, + HttpStatusCode statusCode = HttpStatusCode.OK, + string serverUrl = DefaultBasePath) => MocksHelper.SetupHttpRequest(messageHandler, relativePath, response, statusCode, serverUrl); protected void SetupRequest(string relativePath, HttpResponseMessage response, params MediaTypeHeaderValue[] expectedHeaderValues) => diff --git a/src/SonarQube.Client/SonarQubeService.cs b/src/SonarQube.Client/SonarQubeService.cs index 63a3f72764..eeb48a4d43 100644 --- a/src/SonarQube.Client/SonarQubeService.cs +++ b/src/SonarQube.Client/SonarQubeService.cs @@ -67,7 +67,10 @@ public SonarQubeService(HttpMessageHandler messageHandler, string userAgent, ILo { } - internal /* for testing */ SonarQubeService(HttpMessageHandler messageHandler, string userAgent, ILogger logger, + internal /* for testing */ SonarQubeService( + HttpMessageHandler messageHandler, + string userAgent, + ILogger logger, IRequestFactorySelector requestFactorySelector, ISecondaryIssueHashUpdater secondaryIssueHashUpdater, ISSEStreamReaderFactory sseStreamReaderFactory) @@ -110,7 +113,8 @@ private Task InvokeCheckedRequestAsync(Cancellat /// Action that configures a type instance that implements TRequest. /// Cancellation token. /// Returns the result of the request invocation. - private Task InvokeCheckedRequestAsync(Action configure, + private Task InvokeCheckedRequestAsync( + Action configure, CancellationToken token) where TRequest : IRequest { @@ -141,11 +145,7 @@ public async Task ConnectAsync(ConnectionInformation connection, CancellationTok httpClient = new HttpClient(messageHandler) { - BaseAddress = connection.ServerUri, - DefaultRequestHeaders = - { - Authorization = AuthenticationHeaderFactory.Create(connection.Credentials), - }, + BaseAddress = connection.ServerUri, DefaultRequestHeaders = { Authorization = AuthenticationHeaderFactory.Create(connection.Credentials), }, }; httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent); @@ -214,8 +214,7 @@ await InvokeCheckedRequestAsync> GetAllLanguagesAsync(CancellationToken token) => - await InvokeCheckedRequestAsync(token); + public async Task> GetAllLanguagesAsync(CancellationToken token) => await InvokeCheckedRequestAsync(token); public async Task DownloadStaticFileAsync(string pluginKey, string fileName, CancellationToken token) => await InvokeCheckedRequestAsync( @@ -226,8 +225,7 @@ await InvokeCheckedRequestAsync( }, token); - public async Task> GetAllPluginsAsync(CancellationToken token) => - await InvokeCheckedRequestAsync(token); + public async Task> GetAllPluginsAsync(CancellationToken token) => await InvokeCheckedRequestAsync(token); public async Task> GetAllProjectsAsync(string organizationKey, CancellationToken token) => await InvokeCheckedRequestAsync( @@ -268,7 +266,11 @@ public async Task> GetAllQualityProfilesAsync(str token); } - public async Task GetQualityProfileAsync(string projectKey, string organizationKey, SonarQubeLanguage language, CancellationToken token) + public async Task GetQualityProfileAsync( + string projectKey, + string organizationKey, + SonarQubeLanguage language, + CancellationToken token) { var qualityProfiles = await InvokeCheckedRequestAsync( request => @@ -309,8 +311,11 @@ public async Task GetQualityProfileAsync(string project qualityProfile.IsDefault, updatedDate); } - public async Task GetRoslynExportProfileAsync(string qualityProfileName, - string organizationKey, SonarQubeLanguage language, CancellationToken token) => + public async Task GetRoslynExportProfileAsync( + string qualityProfileName, + string organizationKey, + SonarQubeLanguage language, + CancellationToken token) => await InvokeCheckedRequestAsync( request => { @@ -320,8 +325,11 @@ await InvokeCheckedRequestAsync> GetSuppressedIssuesAsync(string projectKey, string branch, - string[] issueKeys, CancellationToken token) => + public async Task> GetSuppressedIssuesAsync( + string projectKey, + string branch, + string[] issueKeys, + CancellationToken token) => await InvokeCheckedRequestAsync( request => { @@ -332,7 +340,11 @@ await InvokeCheckedRequestAsync( }, token); - public async Task> GetIssuesForComponentAsync(string projectKey, string branch, string componentKey, string ruleId, + public async Task> GetIssuesForComponentAsync( + string projectKey, + string branch, + string componentKey, + string ruleId, CancellationToken token) { return await InvokeCheckedRequestAsync( @@ -346,7 +358,9 @@ public async Task> GetIssuesForComponentAsync(string proje token); } - public async Task> GetNotificationEventsAsync(string projectKey, DateTimeOffset eventsSince, + public async Task> GetNotificationEventsAsync( + string projectKey, + DateTimeOffset eventsSince, CancellationToken token) => await InvokeCheckedRequestAsync( request => @@ -365,17 +379,21 @@ await InvokeCheckedRequestAsync( }, token); - public async Task> SearchFilesByNameAsync(string projectKey, string branch, string fileName, CancellationToken token) + public async Task> SearchFilesByNameAsync( + string projectKey, + string branch, + string fileName, + CancellationToken token) { return await InvokeCheckedRequestAsync( - request => - { - request.ProjectKey = projectKey; - request.BranchName = branch; - request.FileName = fileName; - }, - token - ); + request => + { + request.ProjectKey = projectKey; + request.BranchName = branch; + request.FileName = fileName; + }, + token + ); } public async Task> GetRulesAsync(bool isActive, string qualityProfileKey, CancellationToken token) => @@ -385,7 +403,7 @@ await InvokeCheckedRequestAsync( request.IsActive = isActive; request.QualityProfileKey = qualityProfileKey; }, - token); + token); public async Task GetRuleByKeyAsync(string ruleKey, string qualityProfileKey, CancellationToken token) { @@ -395,7 +413,7 @@ public async Task GetRuleByKeyAsync(string ruleKey, string qualit request.RuleKey = ruleKey; request.QualityProfileKey = qualityProfileKey; }, - token); + token); Debug.Assert(rules.Length <= 1); return rules.FirstOrDefault(); @@ -409,7 +427,8 @@ await InvokeCheckedRequestAsync( }, token); - public async Task> SearchHotspotsAsync(string projectKey, string branch, CancellationToken token) => await InvokeCheckedRequestAsync( + public async Task> SearchHotspotsAsync(string projectKey, string branch, CancellationToken token) => + await InvokeCheckedRequestAsync( request => { request.BranchKey = branch; @@ -417,7 +436,11 @@ public async Task> SearchHotspotsAsync(string proj }, token); - public async Task TransitionIssueAsync(string issueKey, SonarQubeIssueTransition transition, string optionalComment, CancellationToken token) + public async Task TransitionIssueAsync( + string issueKey, + SonarQubeIssueTransition transition, + string optionalComment, + CancellationToken token) { var transitionResult = await InvokeCheckedRequestAsync( request => From 78df95d97fbb9ef9984319e4aee05de67ed84af3 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 10:02:11 +0100 Subject: [PATCH 3/8] SLVS-1732 Fix SSF-694 --- .../CredentialsExtensionMethodsTests.cs | 105 ++++++++++++++++++ .../SolutionBindingCredentialsLoaderTests.cs | 62 ++++++++++- .../Persistence/TokenAuthCredentialsTests.cs | 61 ++++++++++ ...ServerConnectionsRepositoryAdapterTests.cs | 9 +- .../SlCoreConnectionAdapterTests.cs | 12 +- .../ICredentialsExtensionMethods.cs | 50 +++++++++ .../SolutionBindingCredentialsLoader.cs | 16 +-- .../Persistence/TokenAuthCredentials.cs | 35 ++++++ .../ServerConnectionsRepositoryAdapter.cs | 2 +- src/ConnectedMode/SlCoreConnectionAdapter.cs | 17 +-- .../UI/Credentials/ICredentialsModel.cs | 10 +- .../MefServices/MefSonarQubeService.cs | 4 +- .../AuthenticationHeaderFactoryTests.cs | 84 +++++++++++++- .../Helpers/AuthenticationHeaderFactory.cs | 53 +++++++-- .../Models/IConnectionCredentials.cs | 5 + src/SonarQube.Client/SonarQubeService.cs | 56 +++++++--- 16 files changed, 503 insertions(+), 78 deletions(-) create mode 100644 src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs create mode 100644 src/ConnectedMode.UnitTests/Persistence/TokenAuthCredentialsTests.cs create mode 100644 src/ConnectedMode/Persistence/ICredentialsExtensionMethods.cs create mode 100644 src/ConnectedMode/Persistence/TokenAuthCredentials.cs diff --git a/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs b/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs new file mode 100644 index 0000000000..341104e4dd --- /dev/null +++ b/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs @@ -0,0 +1,105 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Microsoft.Alm.Authentication; +using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Helpers; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; + +[TestClass] +public class CredentialsExtensionMethodsTests +{ + [TestMethod] + public void ToCredential_NullCredentials_Throws() => Assert.ThrowsException(() => ((ICredentials)null).ToCredential()); + + [TestMethod] + public void ToCredential_BasicAuthCredentials_ReturnsExpected() + { + var basicAuthCredentials = new BasicAuthCredentials("user", "pwd".ToSecureString()); + + var result = basicAuthCredentials.ToCredential(); + + result.Username.Should().Be(basicAuthCredentials.UserName); + result.Password.Should().Be(basicAuthCredentials.Password.ToUnsecureString()); + } + + [TestMethod] + public void ToCredential_TokenAuthCredentials_ReturnsExpected() + { + var tokenAuthCredentials = new TokenAuthCredentials("token".ToSecureString()); + + var result = tokenAuthCredentials.ToCredential(); + + result.Username.Should().Be(tokenAuthCredentials.Token.ToUnsecureString()); + result.Password.Should().Be(string.Empty); + } + + [TestMethod] + public void ToICredentials_UsernameIsEmpty_ReturnsBasicAuthCredentialsWithPasswordAsToken() + { + var credential = new Credential(string.Empty, "token"); + + var result = credential.ToICredentials(); + + var basicAuth = result as BasicAuthCredentials; + basicAuth.Should().NotBeNull(); + basicAuth.UserName.Should().Be(credential.Username); + basicAuth.Password.ToUnsecureString().Should().Be(credential.Password); + } + + /// + /// For backward compatibility + /// + + [TestMethod] + public void ToICredentials_PasswordIsEmpty_ReturnsTokenAuthCredentialsWithUsernameAsToken() + { + var credential = new Credential("token", string.Empty); + + var result = credential.ToICredentials(); + + var tokenAuth = result as TokenAuthCredentials; + tokenAuth.Should().NotBeNull(); + tokenAuth.Token.ToUnsecureString().Should().Be(credential.Username); + } + + [TestMethod] + public void ToICredentials_PasswordAndUsernameFilled_ReturnsBasicAuthCredentials() + { + var credential = new Credential("username", "pwd"); + + var result = credential.ToICredentials(); + + var basicAuth = result as BasicAuthCredentials; + basicAuth.Should().NotBeNull(); + basicAuth.UserName.Should().Be(credential.Username); + basicAuth.Password.ToUnsecureString().Should().Be(credential.Password); + } + + [TestMethod] + public void ToICredentials_Null_ReturnsNull() + { + var result = ((Credential)null).ToICredentials(); + + result.Should().BeNull(); + } +} diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs index d92a4bd945..024db3b812 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs @@ -53,15 +53,17 @@ public void Ctor_NullStore_Exception() public void Load_ServerUriIsNull_Null() { var actual = testSubject.Load(null); + actual.Should().Be(null); } [TestMethod] public void Load_NoCredentials_Null() { - store.ReadCredentials(mockUri).Returns(null as Credential); + MockReadCredentials(mockUri, null); var actual = testSubject.Load(mockUri); + actual.Should().Be(null); } @@ -69,14 +71,38 @@ public void Load_NoCredentials_Null() public void Load_CredentialsExist_CredentialsWithSecuredString() { var credentials = new Credential("user", "password"); - store - .ReadCredentials(Arg.Is(t => t.ActualUri == mockUri)) - .Returns(credentials); + MockReadCredentials(mockUri, credentials); var actual = testSubject.Load(mockUri); + actual.Should().BeEquivalentTo(new BasicAuthCredentials("user", "password".ToSecureString())); } + [TestMethod] + public void Load_CredentialsExist_UsernameIsEmpty_BasicAuthCredentialsWithSecuredString() + { + var credentials = new Credential(string.Empty, "token"); + MockReadCredentials(mockUri, credentials); + + var actual = testSubject.Load(mockUri); + + actual.Should().BeEquivalentTo(new BasicAuthCredentials(String.Empty, "token".ToSecureString())); + } + + /// + /// For backward compatibility + /// + [TestMethod] + public void Load_CredentialsExist_PasswordIsEmpty_TokenCredentialsWithSecuredString() + { + var credentials = new Credential("token", string.Empty); + MockReadCredentials(mockUri, credentials); + + var actual = testSubject.Load(mockUri); + + actual.Should().BeEquivalentTo(new TokenAuthCredentials("token".ToSecureString())); + } + [TestMethod] public void Save_ServerUriIsNull_CredentialsNotSaved() { @@ -98,8 +124,14 @@ public void Save_CredentialsAreNull_CredentialsNotSaved() [TestMethod] public void Save_CredentialsAreNotBasicAuth_CredentialsNotSaved() { - var mockCredentials = new Mock(); - testSubject.Save(mockCredentials.Object, mockUri); + try + { + testSubject.Save(new Mock().Object, mockUri); + } + catch (Exception _) + { + // ignored + } store.DidNotReceive().WriteCredentials(Arg.Any(), Arg.Any()); } @@ -116,6 +148,19 @@ public void Save_CredentialsAreBasicAuth_CredentialsSavedWithUnsecuredString() Arg.Is(c=> c.Username == "user" && c.Password == "password")); } + [TestMethod] + public void Save_CredentialsAreTokenAuth_CredentialsSavedWithUnsecuredString() + { + var credentials = new TokenAuthCredentials("token".ToSecureString()); + + testSubject.Save(credentials, mockUri); + + store.Received(1) + .WriteCredentials( + Arg.Is(t => t.ActualUri == mockUri), + Arg.Is(c => c.Username == "token" && c.Password == string.Empty)); + } + [TestMethod] public void DeleteCredentials_UriNull_DoesNotCallStoreDeleteCredentials() { @@ -131,5 +176,10 @@ public void DeleteCredentials_UriProvided_CallsStoreDeleteCredentials() store.Received(1).DeleteCredentials(Arg.Any()); } + + private void MockReadCredentials(Uri uri, Credential credentials) => + store + .ReadCredentials(Arg.Is(t => t.ActualUri == uri)) + .Returns(credentials); } } diff --git a/src/ConnectedMode.UnitTests/Persistence/TokenAuthCredentialsTests.cs b/src/ConnectedMode.UnitTests/Persistence/TokenAuthCredentialsTests.cs new file mode 100644 index 0000000000..1fe9f83bb3 --- /dev/null +++ b/src/ConnectedMode.UnitTests/Persistence/TokenAuthCredentialsTests.cs @@ -0,0 +1,61 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.TestInfrastructure; +using SonarQube.Client.Helpers; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; + +[TestClass] +public class TokenAuthCredentialsTests +{ + private const string Token = "token"; + + [TestMethod] + public void Ctor_WhenTokenIsNull_ThrowsArgumentNullException() + { + Action act = () => new TokenAuthCredentials(null); + + act.Should().Throw(); + } + + [TestMethod] + public void Dispose_DisposesPassword() + { + var testSubject = new TokenAuthCredentials(Token.ToSecureString()); + + testSubject.Dispose(); + + Exceptions.Expect(() => testSubject.Token.ToUnsecureString()); + } + + [TestMethod] + public void Clone_ClonesPassword() + { + var testSubject = new TokenAuthCredentials(Token.ToSecureString()); + + var clone = (TokenAuthCredentials)testSubject.Clone(); + + clone.Should().NotBeSameAs(testSubject); + clone.Token.Should().NotBeSameAs(testSubject.Token); + clone.Token.ToUnsecureString().Should().Be(testSubject.Token.ToUnsecureString()); + } +} diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs index 363a1798af..a3478613f3 100644 --- a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -209,7 +209,7 @@ public void TryAddConnection_TokenCredentialsModel_MapsCredentials() testSubject.TryAddConnection(sonarQube, new TokenCredentialsModel(token.CreateSecureString())); serverConnectionsRepository.Received(1) - .TryAdd(Arg.Is(sq => IsExpectedCredentials(sq.Credentials, token, string.Empty))); + .TryAdd(Arg.Is(sq => IsExpectedTokenCredentials(sq.Credentials, token))); } [TestMethod] @@ -257,7 +257,7 @@ public void TryUpdateCredentials_TokenCredentialsModel_MapsCredentials() testSubject.TryUpdateCredentials(sonarQube, new TokenCredentialsModel(token.CreateSecureString())); serverConnectionsRepository.Received(1) - .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, token, string.Empty))); + .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedTokenCredentials(x, token))); } [TestMethod] @@ -370,6 +370,11 @@ private static bool IsExpectedCredentials(ICredentials credentials, string expec return credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; } + private static bool IsExpectedTokenCredentials(ICredentials credentials, string expectedToken) + { + return credentials is TokenAuthCredentials tokenAuthCredentials && tokenAuthCredentials.Token?.ToUnsecureString() == expectedToken; + } + private void MockTryGet(string connectionId, bool expectedResponse, ServerConnection expectedServerConnection) { serverConnectionsRepository.TryGet(connectionId, out _).Returns(callInfo => diff --git a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs index d6c2e72896..e4c8e8f027 100644 --- a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Security; using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.ConnectedMode.UI.Credentials; using SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection; @@ -32,15 +31,16 @@ using SonarLint.VisualStudio.SLCore.Service.Connection; using SonarLint.VisualStudio.SLCore.Service.Connection.Models; using SonarLint.VisualStudio.TestInfrastructure; +using SonarQube.Client.Helpers; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; [TestClass] public class SlCoreConnectionAdapterTests { - private static readonly BasicAuthCredentials ValidToken = new ("I_AM_JUST_A_TOKEN", new SecureString()); - private readonly ServerConnection.SonarQube sonarQubeConnection = new(new Uri("http://localhost:9000/"), new ServerConnectionSettings(true), ValidToken); - private readonly ServerConnection.SonarCloud sonarCloudConnection = new("myOrg", new ServerConnectionSettings(true), ValidToken); + private static readonly TokenAuthCredentials ValidTokenAuth = new ("I_AM_JUST_A_TOKEN".ToSecureString()); + private readonly ServerConnection.SonarQube sonarQubeConnection = new(new Uri("http://localhost:9000/"), new ServerConnectionSettings(true), ValidTokenAuth); + private readonly ServerConnection.SonarCloud sonarCloudConnection = new("myOrg", new ServerConnectionSettings(true), ValidTokenAuth); private SlCoreConnectionAdapter testSubject; private ISLCoreServiceProvider slCoreServiceProvider; @@ -280,7 +280,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarQubeWithToken_CallsGetAll await testSubject.GetAllProjectsAsync(sonarQubeConnection); await connectionConfigurationSlCoreService.Received(1) - .GetAllProjectsAsync(Arg.Is(x => IsExpectedSonarQubeConnectionParams(x.transientConnection, ValidToken.UserName))); + .GetAllProjectsAsync(Arg.Is(x => IsExpectedSonarQubeConnectionParams(x.transientConnection, ValidTokenAuth.Token.ToUnsecureString()))); } [TestMethod] @@ -302,7 +302,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarCloudWithToken_CallsGetAl await testSubject.GetAllProjectsAsync(sonarCloudConnection); await connectionConfigurationSlCoreService.Received(1) - .GetAllProjectsAsync(Arg.Is(x => IsExpectedSonarCloudConnectionParams(x.transientConnection, ValidToken.UserName))); + .GetAllProjectsAsync(Arg.Is(x => IsExpectedSonarCloudConnectionParams(x.transientConnection, ValidTokenAuth.Token.ToUnsecureString()))); } [TestMethod] diff --git a/src/ConnectedMode/Persistence/ICredentialsExtensionMethods.cs b/src/ConnectedMode/Persistence/ICredentialsExtensionMethods.cs new file mode 100644 index 0000000000..146c5a0629 --- /dev/null +++ b/src/ConnectedMode/Persistence/ICredentialsExtensionMethods.cs @@ -0,0 +1,50 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Microsoft.Alm.Authentication; +using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Helpers; + +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +internal static class CredentialsExtensionMethods +{ + internal static Credential ToCredential(this ICredentials credentials) => + credentials switch + { + BasicAuthCredentials basicAuthCredentials => new Credential(basicAuthCredentials.UserName, basicAuthCredentials.Password.ToUnsecureString()), + // the ICredentialStoreService requires a username, but then proceeds to store the username as the password and a hard-coded string as the username + TokenAuthCredentials tokenAuthCredentials => new Credential(tokenAuthCredentials.Token.ToUnsecureString(), string.Empty), + _ => throw new NotSupportedException($"Unexpected credentials type: {credentials?.GetType()}") + }; + + internal static ICredentials ToICredentials(this Credential credential) + { + if (credential is null) + { + return null; + } + if (credential.Password == string.Empty) + { + return new TokenAuthCredentials(credential.Username.ToSecureString()); + } + return new BasicAuthCredentials(credential.Username, credential.Password.ToSecureString()); + } +} diff --git a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs index 6232873efd..9611bce48c 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs @@ -51,27 +51,17 @@ public ICredentials Load(Uri boundServerUri) } var credentials = store.ReadCredentials(boundServerUri); - return credentials == null - ? null - : new BasicAuthCredentials(credentials.Username, credentials.Password.ToSecureString()); + return credentials.ToICredentials(); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", - "S3215:\"interface\" instances should not be cast to concrete types", - Justification = "Casting as BasicAuthCredentials is because it's the only credential type we support. Once we add more we need to think again on how to refactor the code to avoid this", - Scope = "member", - Target = "~M:SonarLint.VisualStudio.Integration.Persistence.FileBindingSerializer.WriteBindingInformation(System.String,SonarLint.VisualStudio.Integration.Persistence.BoundProject)~System.Boolean")] public void Save(ICredentials credentials, Uri boundServerUri) { - if (boundServerUri == null || !(credentials is BasicAuthCredentials basicCredentials)) + if (boundServerUri == null || credentials is null) { return; } - Debug.Assert(basicCredentials.UserName != null, "User name is not expected to be null"); - Debug.Assert(basicCredentials.Password != null, "Password name is not expected to be null"); - - var credentialToSave = new Credential(basicCredentials.UserName, basicCredentials.Password.ToUnsecureString()); + var credentialToSave = credentials.ToCredential(); store.WriteCredentials(boundServerUri, credentialToSave); } } diff --git a/src/ConnectedMode/Persistence/TokenAuthCredentials.cs b/src/ConnectedMode/Persistence/TokenAuthCredentials.cs new file mode 100644 index 0000000000..11b5742095 --- /dev/null +++ b/src/ConnectedMode/Persistence/TokenAuthCredentials.cs @@ -0,0 +1,35 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Security; +using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Helpers; +using SonarQube.Client.Models; + +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +internal sealed class TokenAuthCredentials(SecureString token) : ICredentials, ITokenCredentials +{ + public SecureString Token { get; } = token ?? throw new ArgumentNullException(nameof(token)); + + public void Dispose() => Token?.Dispose(); + + public object Clone() => new TokenAuthCredentials(Token.CopyAsReadOnly()); +} diff --git a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs index 5e684787ea..f405c500c1 100644 --- a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs +++ b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs @@ -102,7 +102,7 @@ private static ICredentials MapCredentials(ICredentialsModel credentialsModel) switch (credentialsModel) { case TokenCredentialsModel tokenCredentialsModel: - return new BasicAuthCredentials(tokenCredentialsModel.Token.ToUnsecureString(), new SecureString()); + return new TokenAuthCredentials(tokenCredentialsModel.Token); case UsernamePasswordModel usernameCredentialsModel: return new BasicAuthCredentials(usernameCredentialsModel.Username, usernameCredentialsModel.Password); default: diff --git a/src/ConnectedMode/SlCoreConnectionAdapter.cs b/src/ConnectedMode/SlCoreConnectionAdapter.cs index 169433bd83..b78a3c6033 100644 --- a/src/ConnectedMode/SlCoreConnectionAdapter.cs +++ b/src/ConnectedMode/SlCoreConnectionAdapter.cs @@ -268,16 +268,11 @@ private static Either GetEitherForUsernamePasswor return Either.CreateRight(new UsernamePasswordDto(username, password)); } - private static Either MapCredentials(ICredentials credentials) - { - if (credentials == null) + private static Either MapCredentials(ICredentials credentials) => + credentials switch { - throw new ArgumentException($"Unexpected {nameof(ICredentialsModel)} argument"); - } - - var basicAuthCredentials = (BasicAuthCredentials) credentials; - return basicAuthCredentials.Password?.Length > 0 - ? GetEitherForUsernamePassword(basicAuthCredentials.UserName, basicAuthCredentials.Password.ToUnsecureString()) - : GetEitherForToken(basicAuthCredentials.UserName); - } + BasicAuthCredentials basicAuthCredentials => GetEitherForUsernamePassword(basicAuthCredentials.UserName, basicAuthCredentials.Password.ToUnsecureString()), + TokenAuthCredentials tokenAuthCredentials => GetEitherForToken(tokenAuthCredentials.Token.ToUnsecureString()), + _ => throw new ArgumentException($"Unexpected {nameof(ICredentialsModel)} argument") + }; } diff --git a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs index 2e14786ec5..5f3c871943 100644 --- a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs +++ b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs @@ -34,10 +34,7 @@ public class TokenCredentialsModel(SecureString token) : ICredentialsModel { public SecureString Token { get; } = token; - public ICredentials ToICredentials() - { - return new BasicAuthCredentials(Token.ToUnsecureString(), new SecureString()); - } + public ICredentials ToICredentials() => new TokenAuthCredentials(Token); } public class UsernamePasswordModel(string username, SecureString password) : ICredentialsModel @@ -45,8 +42,5 @@ public class UsernamePasswordModel(string username, SecureString password) : ICr public string Username { get; } = username; public SecureString Password { get; } = password; - public ICredentials ToICredentials() - { - return new BasicAuthCredentials(Username, Password); - } + public ICredentials ToICredentials() => new BasicAuthCredentials(Username, Password); } diff --git a/src/Integration/MefServices/MefSonarQubeService.cs b/src/Integration/MefServices/MefSonarQubeService.cs index 22ec5f59a9..5509203693 100644 --- a/src/Integration/MefServices/MefSonarQubeService.cs +++ b/src/Integration/MefServices/MefSonarQubeService.cs @@ -58,11 +58,11 @@ public MefSonarQubeService(ILogger logger) this.threadHandling = threadHandling; } - protected override async Task InvokeUncheckedRequestAsync(Action configure, CancellationToken token) + protected override async Task InvokeUncheckedRequestAsync(Action configure, HttpClient http, CancellationToken token) { CodeMarkers.Instance.WebClientCallStart(typeof(TRequest).Name); - Func> asyncMethod = () => base.InvokeUncheckedRequestAsync(configure, token); + Func> asyncMethod = () => base.InvokeUncheckedRequestAsync(configure, http, token); var result = await threadHandling.RunOnBackgroundThread(asyncMethod); diff --git a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs index 9653ae7d77..6fb5a6894c 100644 --- a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs +++ b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs @@ -28,6 +28,7 @@ namespace SonarQube.Client.Tests.Helpers public class AuthenticationHeaderFactoryTests { private const string Password = "password"; + private const string Token = "token"; private const string Username = "username"; [TestMethod] @@ -89,6 +90,26 @@ public void Create_BasicAuth_UsernameIsNull_Throws() act.Should().Throw(); } + [TestMethod] + public void Create_BasicAuth_PasswordIsNull_Throws() + { + var credentials = MockBasicAuthCredentials(Username, null); + + var act = () => AuthenticationHeaderFactory.Create(credentials); + + act.Should().Throw(); + } + + [TestMethod] + public void Create_BasicAuth_PasswordIsEmpty_Throws() + { + var credentials = MockBasicAuthCredentials(Username, "".ToSecureString()); + + var act = () => AuthenticationHeaderFactory.Create(credentials); + + act.Should().Throw(); + } + [TestMethod] public void Create_BasicAuth_CredentialsProvided_ReturnsBasicScheme() { @@ -100,9 +121,59 @@ public void Create_BasicAuth_CredentialsProvided_ReturnsBasicScheme() AssertAreEqualUserNameAndPassword(Username, Password, authenticationHeaderValue.Parameter); } - private void AssertAreEqualUserNameAndPassword( - string expectedUser, - string expectedPassword, + [TestMethod] + public void Create_BearerSupported_TokenIsNull_Throws() + { + var credentials = MockTokenCredentials(null); + + var act = () => AuthenticationHeaderFactory.Create(credentials); + + act.Should().Throw(); + } + + [TestMethod] + public void Create_BearerSupported_TokenIsEmpty_Throws() + { + var credentials = MockTokenCredentials( "".ToSecureString()); + + var act = () => AuthenticationHeaderFactory.Create(credentials); + + act.Should().Throw(); + } + + [TestMethod] + public void Create_BearerSupported_TokenIsFilled_ReturnsBearerScheme() + { + var credentials = MockTokenCredentials(Token.ToSecureString()); + + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(credentials); + + authenticationHeaderValue.Scheme.Should().Be("Bearer"); + authenticationHeaderValue.Parameter.Should().Be(Token); + } + + [TestMethod] + public void Create_BearerNotSupported_TokenIsFilled_ReturnsBasicScheme() + { + var credentials = MockTokenCredentials(Token.ToSecureString()); + + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer:false); + + authenticationHeaderValue.Scheme.Should().Be("Basic"); + AssertAreEqualUserNameAndPassword(Token, string.Empty, authenticationHeaderValue.Parameter); + } + + [TestMethod] + public void Create_BearerNotSupported_TokenIsEmpty_Throws() + { + var credentials = MockTokenCredentials("".ToSecureString()); + + var act = () => AuthenticationHeaderFactory.Create(credentials, shouldUseBearer:false); + + act.Should().Throw(); + } + + private static void AssertAreEqualUserNameAndPassword(string expectedUser, string expectedPassword, string userAndPasswordBase64String) { @@ -128,5 +199,12 @@ private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, S mock.Password.Returns(password); return mock; } + + private static ITokenCredentials MockTokenCredentials(SecureString token) + { + var mock = Substitute.For(); + mock.Token.Returns(token); + return mock; + } } } diff --git a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs index f5637c728a..73ae1a2477 100644 --- a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs +++ b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs @@ -28,28 +28,48 @@ namespace SonarQube.Client.Helpers public static class AuthenticationHeaderFactory { internal const string BasicAuthCredentialSeparator = ":"; + private const string BearerScheme = "Bearer"; + private const string BasicScheme = "Basic"; /// /// Encoding used to create the basic authentication token /// internal static readonly Encoding BasicAuthEncoding = Encoding.UTF8; - public static AuthenticationHeaderValue Create(IConnectionCredentials credentials) + public static AuthenticationHeaderValue Create(IConnectionCredentials credentials, bool shouldUseBearer = true) { - if (credentials is IBasicAuthCredentials basicAuthCredentials) + switch (credentials) { - ValidateCredentials(basicAuthCredentials); - return new AuthenticationHeaderValue("Basic", GetBasicAuthToken(basicAuthCredentials.UserName, basicAuthCredentials.Password)); - // See more info: https://www.visualstudio.com/en-us/integrate/get-started/auth/overview + case ITokenCredentials tokenCredentials: + { + ValidateSecureString(tokenCredentials.Token, nameof(tokenCredentials.Token)); + return CreateAuthenticationHeaderValueForTokenAuth(shouldUseBearer, tokenCredentials); + } + case IBasicAuthCredentials basicAuthCredentials: + { + ValidateCredentials(basicAuthCredentials); + return CreateAuthenticationHeaderValueForBasicAuth(basicAuthCredentials.UserName, basicAuthCredentials.Password); + } + case INoCredentials: + return null; + default: + Debug.Fail("Unsupported Authentication: " + credentials?.GetType()); + return null; } - if (credentials is INoCredentials) + } + + private static AuthenticationHeaderValue CreateAuthenticationHeaderValueForTokenAuth(bool shouldUseBearer, ITokenCredentials tokenCredentials) + { + if (shouldUseBearer) { - return null; + return new AuthenticationHeaderValue(BearerScheme, tokenCredentials.Token.ToUnsecureString()); } - Debug.Fail("Unsupported Authentication: " + credentials?.GetType()); - return null; + return CreateAuthenticationHeaderValueForBasicAuth(tokenCredentials.Token.ToUnsecureString(), new SecureString()); } + private static AuthenticationHeaderValue CreateAuthenticationHeaderValueForBasicAuth(string username, SecureString password) => new(BasicScheme, GetBasicAuthToken(username, password)); + + // See more info: https://www.visualstudio.com/en-us/integrate/get-started/auth/overview internal static string GetBasicAuthToken(string user, SecureString password) { if (!string.IsNullOrEmpty(user) && user.Contains(BasicAuthCredentialSeparator)) @@ -63,11 +83,20 @@ internal static string GetBasicAuthToken(string user, SecureString password) user, password.ToUnsecureString()))); } - private static void ValidateCredentials(IBasicAuthCredentials basicAuthCredentials) + private static void ValidateCredentials(IBasicAuthCredentials credentials) + { + if (string.IsNullOrEmpty(credentials.UserName)) + { + throw new ArgumentException(nameof(IBasicAuthCredentials.UserName)); + } + ValidateSecureString(credentials.Password, nameof(IBasicAuthCredentials.Password)); + } + + private static void ValidateSecureString(SecureString secureString, string parameterName) { - if (string.IsNullOrEmpty(basicAuthCredentials.UserName)) + if (secureString.IsNullOrEmpty()) { - throw new ArgumentException(nameof(basicAuthCredentials.UserName)); + throw new ArgumentException(parameterName); } } } diff --git a/src/SonarQube.Client/Models/IConnectionCredentials.cs b/src/SonarQube.Client/Models/IConnectionCredentials.cs index 88cbe512ad..f48595c5fa 100644 --- a/src/SonarQube.Client/Models/IConnectionCredentials.cs +++ b/src/SonarQube.Client/Models/IConnectionCredentials.cs @@ -32,6 +32,11 @@ public interface IBasicAuthCredentials : IConnectionCredentials public SecureString Password { get; } } +public interface ITokenCredentials : IConnectionCredentials +{ + public SecureString Token { get; } +} + public interface INoCredentials : IConnectionCredentials { } diff --git a/src/SonarQube.Client/SonarQubeService.cs b/src/SonarQube.Client/SonarQubeService.cs index eeb48a4d43..ac0a0e247c 100644 --- a/src/SonarQube.Client/SonarQubeService.cs +++ b/src/SonarQube.Client/SonarQubeService.cs @@ -46,6 +46,7 @@ public class SonarQubeService : ISonarQubeService, IDisposable private readonly ISecondaryIssueHashUpdater secondaryIssueHashUpdater; private readonly ISSEStreamReaderFactory sseStreamReaderFactory; + private const string MinSqVersionSupportingBearer = "10.4"; private HttpClient httpClient; private ServerInfo currentServerInfo; @@ -127,13 +128,23 @@ private Task InvokeCheckedRequestAsync( /// Executes the call without checking whether the connection to the server has been established. This should only normally be used directly while connecting. /// Other uses should call . /// - protected virtual async Task InvokeUncheckedRequestAsync(Action configure, CancellationToken token) + private async Task InvokeUncheckedRequestAsync(Action configure, CancellationToken token) + where TRequest : IRequest + { + return await InvokeUncheckedRequestAsync(configure, httpClient, token); + } + + /// + /// Executes the call without checking whether the connection to the server has been established. This should only normally be used directly while connecting. + /// Other uses should call . + /// + protected virtual async Task InvokeUncheckedRequestAsync(Action configure, HttpClient httpClientParam, CancellationToken token) where TRequest : IRequest { var request = requestFactory.Create(currentServerInfo); configure(request); - var result = await request.InvokeAsync(httpClient, token); + var result = await request.InvokeAsync(httpClientParam, token); return result; } @@ -143,13 +154,6 @@ public async Task ConnectAsync(ConnectionInformation connection, CancellationTok logger.Info($"Connecting to '{connection.ServerUri}'."); logger.Debug($"IsConnected is {IsConnected}."); - httpClient = new HttpClient(messageHandler) - { - BaseAddress = connection.ServerUri, DefaultRequestHeaders = { Authorization = AuthenticationHeaderFactory.Create(connection.Credentials), }, - }; - - httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent); - requestFactory = requestFactorySelector.Select(connection.IsSonarCloud, logger); try @@ -157,14 +161,12 @@ public async Task ConnectAsync(ConnectionInformation connection, CancellationTok var serverTypeDescription = connection.IsSonarCloud ? "SonarCloud" : "SonarQube"; logger.Debug($"Getting the version of {serverTypeDescription}..."); - - var versionResponse = await InvokeUncheckedRequestAsync(request => { }, token); - var serverInfo = new ServerInfo(Version.Parse(versionResponse), connection.IsSonarCloud ? ServerType.SonarCloud : ServerType.SonarQube); + var serverInfo = await GetServerInfo(connection, token); logger.Info($"Connected to {serverTypeDescription} '{serverInfo.Version}'."); + httpClient = CreateHttpClient(connection.ServerUri, connection.Credentials, ShouldUseBearer(serverInfo)); logger.Debug($"Validating the credentials..."); - var credentialResponse = await InvokeUncheckedRequestAsync(request => { }, token); if (!credentialResponse) { @@ -172,7 +174,6 @@ public async Task ConnectAsync(ConnectionInformation connection, CancellationTok } logger.Debug($"Credentials accepted."); - currentServerInfo = serverInfo; } catch @@ -576,5 +577,32 @@ public void Dispose() } return organizationKey; } + + private static bool ShouldUseBearer(ServerInfo serverInfo) + { + return serverInfo.ServerType == ServerType.SonarCloud || serverInfo.Version >= Version.Parse(MinSqVersionSupportingBearer); + } + + private async Task GetServerInfo(ConnectionInformation connection, CancellationToken token) + { + var http = CreateHttpClient(connection.ServerUri, new NoCredentials(), shouldUseBearer:true); + var versionResponse = await InvokeUncheckedRequestAsync(request => { }, http, token); + var serverInfo = new ServerInfo(Version.Parse(versionResponse), connection.IsSonarCloud ? ServerType.SonarCloud : ServerType.SonarQube); + return serverInfo; + } + + private HttpClient CreateHttpClient(Uri baseAddress, IConnectionCredentials credentials, bool shouldUseBearer) + { + var client = new HttpClient(messageHandler) + { + BaseAddress = baseAddress, + DefaultRequestHeaders = + { + Authorization = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer), + }, + }; + client.DefaultRequestHeaders.Add("User-Agent", userAgent); + return client; + } } } From 64035763f7c373aeec5c592926fe09b54839b194 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 10:04:31 +0100 Subject: [PATCH 4/8] CaYC: cleanup code applied in touched files --- .../CredentialsExtensionMethodsTests.cs | 1 - .../SolutionBindingCredentialsLoaderTests.cs | 4 +-- ...ServerConnectionsRepositoryAdapterTests.cs | 36 +++++++++---------- .../SlCoreConnectionAdapterTests.cs | 34 ++++++++---------- .../SolutionBindingCredentialsLoader.cs | 2 +- .../ServerConnectionsRepositoryAdapter.cs | 7 +++- src/ConnectedMode/SlCoreConnectionAdapter.cs | 22 +++++++----- .../UI/Credentials/ICredentialsModel.cs | 2 +- .../MefServices/MefSonarQubeService.cs | 9 ++--- .../AuthenticationHeaderFactoryTests.cs | 12 ++++--- .../Helpers/AuthenticationHeaderFactory.cs | 16 ++++----- src/SonarQube.Client/SonarQubeService.cs | 11 ++---- 12 files changed, 77 insertions(+), 79 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs b/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs index 341104e4dd..70d5147022 100644 --- a/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/CredentialsExtensionMethodsTests.cs @@ -69,7 +69,6 @@ public void ToICredentials_UsernameIsEmpty_ReturnsBasicAuthCredentialsWithPasswo /// /// For backward compatibility /// - [TestMethod] public void ToICredentials_PasswordIsEmpty_ReturnsTokenAuthCredentialsWithUsernameAsToken() { diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs index 024db3b812..f4a14f9411 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs @@ -144,8 +144,8 @@ public void Save_CredentialsAreBasicAuth_CredentialsSavedWithUnsecuredString() store.Received(1) .WriteCredentials( - Arg.Is(t => t.ActualUri == mockUri), - Arg.Is(c=> c.Username == "user" && c.Password == "password")); + Arg.Is(t => t.ActualUri == mockUri), + Arg.Is(c => c.Username == "user" && c.Password == "password")); } [TestMethod] diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs index a3478613f3..ea907374e9 100644 --- a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -41,8 +41,8 @@ public void TestInitialize() } [TestMethod] - public void MefCtor_CheckIsExported() - => MefTestHelpers.CheckTypeCanBeImported(MefTestHelpers.CreateExport()); + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported(MefTestHelpers.CreateExport()); [TestMethod] public void TryGetServerConnectionById_CallServerConnectionsRepository() @@ -55,10 +55,10 @@ public void TryGetServerConnectionById_CallServerConnectionsRepository() }); testSubject.TryGet(new ConnectionInfo("myOrg", ConnectionServerType.SonarCloud), out var serverConnection); - + serverConnection.Should().Be(expectedServerConnection); } - + [TestMethod] public void TryGetAllConnections_CallServerConnectionsRepository() { @@ -102,7 +102,7 @@ public void TryGetAllConnections_HasOneSonarQubeConnection_ReturnsOneMappedConne public void TryGetAllConnections_ReturnsStatusFromSlCore(bool expectedStatus) { var sonarCloud = CreateSonarCloudServerConnection(); - MockServerConnections([sonarCloud], succeeded:expectedStatus); + MockServerConnections([sonarCloud], succeeded: expectedStatus); var succeeded = testSubject.TryGetAllConnections(out _); @@ -170,7 +170,7 @@ public void TryAddConnection_ReturnsStatusFromSlCore(bool expectedStatus) [TestMethod] [DataRow(true)] - [DataRow(false)] + [DataRow(false)] public void TryAddConnection_AddsSonarCloudConnection_CallsSlCoreWithMappedConnection(bool enableSmartNotifications) { var sonarCloud = CreateSonarCloudConnection(enableSmartNotifications); @@ -234,7 +234,7 @@ public void TryAddConnection_NullCredentials_TriesAddingAConnectionWithNoCredent serverConnectionsRepository.Received(1).TryAdd(Arg.Is(sq => sq.Credentials == null)); } - + [TestMethod] [DataRow(true)] [DataRow(false)] @@ -259,42 +259,42 @@ public void TryUpdateCredentials_TokenCredentialsModel_MapsCredentials() serverConnectionsRepository.Received(1) .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedTokenCredentials(x, token))); } - + [TestMethod] public void TryUpdateCredentials_UserPasswordModel_MapsCredentials() { var sonarQube = CreateSonarQubeConnection(); const string username = "username"; const string password = "password"; - + testSubject.TryUpdateCredentials(sonarQube, new UsernamePasswordModel(username, password.CreateSecureString())); - + serverConnectionsRepository.Received(1) .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, username, password))); } - + [TestMethod] public void TryUpdateCredentials_SonarQube_MapsConnection() { var sonarQube = CreateSonarQubeConnection(); - + testSubject.TryUpdateCredentials(sonarQube, Substitute.For()); - + serverConnectionsRepository.Received(1) .TryUpdateCredentialsById(Arg.Is(x => x.Equals(sonarQube.Info.Id)), Arg.Any()); } - + [TestMethod] public void TryUpdateCredentials_SonarCloud_MapsConnection() { var sonarCloud = CreateSonarCloudConnection(); - + testSubject.TryUpdateCredentials(sonarCloud, Substitute.For()); - + serverConnectionsRepository.Received(1) .TryUpdateCredentialsById(Arg.Is(x => x.EndsWith(sonarCloud.Info.Id)), Arg.Any()); } - + [TestMethod] public void TryUpdateCredentials_NullCredentials_TriesUpdatingConnectionWithNoCredentials() { @@ -364,7 +364,7 @@ private static Connection CreateSonarQubeConnection(bool enableSmartNotification { return new Connection(new ConnectionInfo("http://localhost:9000/", ConnectionServerType.SonarQube), enableSmartNotifications); } - + private static bool IsExpectedCredentials(ICredentials credentials, string expectedUsername, string expectedPassword) { return credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; diff --git a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs index e4c8e8f027..4e4f165133 100644 --- a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs @@ -38,10 +38,10 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; [TestClass] public class SlCoreConnectionAdapterTests { - private static readonly TokenAuthCredentials ValidTokenAuth = new ("I_AM_JUST_A_TOKEN".ToSecureString()); + private static readonly TokenAuthCredentials ValidTokenAuth = new("I_AM_JUST_A_TOKEN".ToSecureString()); private readonly ServerConnection.SonarQube sonarQubeConnection = new(new Uri("http://localhost:9000/"), new ServerConnectionSettings(true), ValidTokenAuth); private readonly ServerConnection.SonarCloud sonarCloudConnection = new("myOrg", new ServerConnectionSettings(true), ValidTokenAuth); - + private SlCoreConnectionAdapter testSubject; private ISLCoreServiceProvider slCoreServiceProvider; private IThreadHandling threadHandling; @@ -200,7 +200,7 @@ public async Task GetOrganizationsAsync_TokenIsProvided_CallsSlCoreListUserOrgan await testSubject.GetOrganizationsAsync(new TokenCredentialsModel(token.CreateSecureString())); - await connectionConfigurationSlCoreService.Received(1).ListUserOrganizationsAsync(Arg.Is(x=> IsExpectedCredentials(x.credentials, token))); + await connectionConfigurationSlCoreService.Received(1).ListUserOrganizationsAsync(Arg.Is(x => IsExpectedCredentials(x.credentials, token))); } [TestMethod] @@ -257,7 +257,7 @@ public async Task GetAllProjectsAsync_SwitchesToBackgroundThread() { var threadHandlingMock = Substitute.For(); var slCoreConnectionAdapter = new SlCoreConnectionAdapter(slCoreServiceProvider, threadHandlingMock, logger); - + await slCoreConnectionAdapter.GetAllProjectsAsync(sonarQubeConnection); await threadHandlingMock.Received(1).RunOnBackgroundThread(Arg.Any>>>>()); @@ -289,7 +289,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarQubeWithCredentials_Calls const string username = "username"; const string password = "password"; sonarQubeConnection.Credentials = new BasicAuthCredentials(username, password.CreateSecureString()); - + await testSubject.GetAllProjectsAsync(sonarQubeConnection); await connectionConfigurationSlCoreService.Received(1) @@ -333,7 +333,7 @@ public async Task GetAllProjectsAsync_ReturnsResponseFromSlCore() new ServerProject("projKey2", "projName2") ]); } - + [TestMethod] public async Task GetServerProjectByKeyAsync_SwitchesToBackgroundThread() { @@ -344,7 +344,7 @@ public async Task GetServerProjectByKeyAsync_SwitchesToBackgroundThread() await threadHandlingMock.Received(1).RunOnBackgroundThread(Arg.Any>>>()); } - + [TestMethod] public async Task GetServerProjectByKeyAsync_GettingConnectionConfigurationSLCoreServiceFails_ReturnsFailedResponseAndShouldLog() { @@ -356,7 +356,7 @@ public async Task GetServerProjectByKeyAsync_GettingConnectionConfigurationSLCor response.Success.Should().BeFalse(); response.ResponseData.Should().BeNull(); } - + [TestMethod] public async Task GetServerProjectByKeyAsync_SlCoreThrowsException_ReturnsFailedResponseAndShouldLog() { @@ -370,27 +370,24 @@ public async Task GetServerProjectByKeyAsync_SlCoreThrowsException_ReturnsFailed response.Success.Should().BeFalse(); response.ResponseData.Should().BeNull(); } - + [TestMethod] public async Task GetServerProjectByKeyAsync_ProjectNotFound_ReturnsFailedResponse() { - var slCoreResponse = new Dictionary { {"project-key", null} }; + var slCoreResponse = new Dictionary { { "project-key", null } }; connectionConfigurationSlCoreService.GetProjectNamesByKeyAsync(Arg.Any()) .Returns(new GetProjectNamesByKeyResponse(slCoreResponse)); - + var response = await testSubject.GetServerProjectByKeyAsync(sonarCloudConnection, "project-key"); response.Success.Should().BeFalse(); response.ResponseData.Should().BeNull(); } - + [TestMethod] public async Task GetServerProjectByKeyAsync_ProjectFound_ReturnsSuccessResponseAndMappedOrganizations() { - var slCoreResponse = new Dictionary - { - {"project-key", "project-name"} - }; + var slCoreResponse = new Dictionary { { "project-key", "project-name" } }; connectionConfigurationSlCoreService.GetProjectNamesByKeyAsync(Arg.Any()) .Returns(new GetProjectNamesByKeyResponse(slCoreResponse)); var response = await testSubject.GetServerProjectByKeyAsync(sonarQubeConnection, "project-key"); @@ -399,7 +396,6 @@ public async Task GetServerProjectByKeyAsync_ProjectFound_ReturnsSuccessResponse response.ResponseData.Should().BeEquivalentTo(new ServerProject("project-key", "project-name")); } - [TestMethod] public async Task GetAllProjectsAsync_SlCoreValidationThrowsException_ReturnsUnsuccessfulResponse() { @@ -439,7 +435,7 @@ public async Task FuzzySearchProjectsAsync_GettingConnectionConfigurationSLCoreS public async Task FuzzySearchProjectsAsync_CallsSlCoreWithCorrectParams() { var searchTerm = "proj1"; - + await testSubject.FuzzySearchProjectsAsync(sonarCloudConnection, searchTerm); await connectionConfigurationSlCoreService.Received(1).FuzzySearchProjectsAsync( @@ -459,7 +455,7 @@ public async Task FuzzySearchProjectsAsync_ReturnsResponseFromSlCore() response.ResponseData.Count.Should().Be(expectedServerProjects.Count); response.ResponseData.Should().BeEquivalentTo([ new ServerProject("projKey1", "projName1"), - new ServerProject("projKey2", "projName2") + new ServerProject("projKey2", "projName2") ]); } diff --git a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs index 9611bce48c..eac08c5954 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs @@ -36,7 +36,7 @@ public SolutionBindingCredentialsLoader(ICredentialStoreService store) public void DeleteCredentials(Uri boundServerUri) { - if(boundServerUri == null) + if (boundServerUri == null) { return; } diff --git a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs index f405c500c1..8e97961666 100644 --- a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs +++ b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs @@ -30,10 +30,15 @@ namespace SonarLint.VisualStudio.ConnectedMode; public interface IServerConnectionsRepositoryAdapter { bool TryGetAllConnections(out List connections); + bool TryGetAllConnectionsInfo(out List connectionInfos); + bool TryRemoveConnection(ConnectionInfo connectionInfo); + bool TryAddConnection(Connection connection, ICredentialsModel credentialsModel); + bool TryUpdateCredentials(Connection connection, ICredentialsModel credentialsModel); + bool TryGet(ConnectionInfo connectionInfo, out ServerConnection serverConnection); } @@ -61,7 +66,7 @@ public bool TryAddConnection(Connection connection, ICredentialsModel credential serverConnection.Credentials = MapCredentials(credentialsModel); return serverConnectionsRepository.TryAdd(serverConnection); } - + public bool TryUpdateCredentials(Connection connection, ICredentialsModel credentialsModel) { var serverConnection = MapConnection(connection); diff --git a/src/ConnectedMode/SlCoreConnectionAdapter.cs b/src/ConnectedMode/SlCoreConnectionAdapter.cs index b78a3c6033..85242053c9 100644 --- a/src/ConnectedMode/SlCoreConnectionAdapter.cs +++ b/src/ConnectedMode/SlCoreConnectionAdapter.cs @@ -39,22 +39,28 @@ namespace SonarLint.VisualStudio.ConnectedMode; public interface ISlCoreConnectionAdapter { Task ValidateConnectionAsync(ConnectionInfo connectionInfo, ICredentialsModel credentialsModel); + Task>> GetOrganizationsAsync(ICredentialsModel credentialsModel); + Task> GetServerProjectByKeyAsync(ServerConnection serverConnection, string serverProjectKey); + Task>> GetAllProjectsAsync(ServerConnection serverConnection); + Task>> FuzzySearchProjectsAsync(ServerConnection serverConnection, string searchTerm); } public class AdapterResponseWithData(bool success, T responseData) : IResponseStatus { public AdapterResponseWithData() : this(false, default) { } + public bool Success { get; init; } = success; public T ResponseData { get; } = responseData; } public class AdapterResponse(bool success) : IResponseStatus { - public AdapterResponse(): this(false){} + public AdapterResponse() : this(false) { } + public bool Success { get; } = success; } @@ -78,7 +84,7 @@ public SlCoreConnectionAdapter(ISLCoreServiceProvider serviceProvider, IThreadHa public async Task ValidateConnectionAsync(ConnectionInfo connectionInfo, ICredentialsModel credentialsModel) { var credentials = credentialsModel.ToICredentials(); - + var validateConnectionParams = new ValidateConnectionParams(GetTransientConnectionDto(connectionInfo, credentials)); return await ValidateConnectionAsync(validateConnectionParams); } @@ -111,7 +117,7 @@ public Task>> GetOrganizations public Task> GetServerProjectByKeyAsync(ServerConnection serverConnection, string serverProjectKey) { var failedResponse = new AdapterResponseWithData(false, null); - + return threadHandling.RunOnBackgroundThread(async () => { if (!TryGetConnectionConfigurationSlCoreService(out var connectionConfigurationSlCoreService)) @@ -129,7 +135,7 @@ public Task> GetServerProjectByKeyAsync(S logger.LogVerbose(Resources.GetServerProjectByKey_ProjectNotFound, serverProjectKey); return failedResponse; } - + return new AdapterResponseWithData(true, new ServerProject(serverProjectKey, response.projectNamesByKey[serverProjectKey])); } catch (Exception ex) @@ -229,11 +235,11 @@ private bool TryGetConnectionConfigurationSlCoreService(out IConnectionConfigura logger.LogVerbose($"[{nameof(IConnectionConfigurationSLCoreService)}] {SLCoreStrings.ServiceProviderNotInitialized}"); return false; } - + private static Either GetTransientConnectionDto(ConnectionInfo connectionInfo, ICredentials credentials) { var credentialsDto = MapCredentials(credentials); - + return connectionInfo.ServerType switch { ConnectionServerType.SonarQube => Either.CreateLeft( @@ -247,7 +253,7 @@ private static Either GetTransientConnectionDto(ServerConnection serverConnection) { var credentials = MapCredentials(serverConnection.Credentials); - + return serverConnection switch { ServerConnection.SonarQube sonarQubeConnection => Either.CreateLeft( @@ -267,7 +273,7 @@ private static Either GetEitherForUsernamePasswor { return Either.CreateRight(new UsernamePasswordDto(username, password)); } - + private static Either MapCredentials(ICredentials credentials) => credentials switch { diff --git a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs index 5f3c871943..2b05a308bd 100644 --- a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs +++ b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs @@ -41,6 +41,6 @@ public class UsernamePasswordModel(string username, SecureString password) : ICr { public string Username { get; } = username; public SecureString Password { get; } = password; - + public ICredentials ToICredentials() => new BasicAuthCredentials(Username, Password); } diff --git a/src/Integration/MefServices/MefSonarQubeService.cs b/src/Integration/MefServices/MefSonarQubeService.cs index 5509203693..e10d153b79 100644 --- a/src/Integration/MefServices/MefSonarQubeService.cs +++ b/src/Integration/MefServices/MefSonarQubeService.cs @@ -84,14 +84,11 @@ public void Debug(string message) => // This will only be logged if an env var is set logger.LogVerbose(message); - public void Error(string message) => - logger.WriteLine($"ERROR: {message}"); + public void Error(string message) => logger.WriteLine($"ERROR: {message}"); - public void Info(string message) => - logger.WriteLine($"{message}"); + public void Info(string message) => logger.WriteLine($"{message}"); - public void Warning(string message) => - logger.WriteLine($"WARNING: {message}"); + public void Warning(string message) => logger.WriteLine($"WARNING: {message}"); } } } diff --git a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs index 6fb5a6894c..f758a1cbe7 100644 --- a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs +++ b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs @@ -103,7 +103,7 @@ public void Create_BasicAuth_PasswordIsNull_Throws() [TestMethod] public void Create_BasicAuth_PasswordIsEmpty_Throws() { - var credentials = MockBasicAuthCredentials(Username, "".ToSecureString()); + var credentials = MockBasicAuthCredentials(Username, "".ToSecureString()); var act = () => AuthenticationHeaderFactory.Create(credentials); @@ -134,7 +134,7 @@ public void Create_BearerSupported_TokenIsNull_Throws() [TestMethod] public void Create_BearerSupported_TokenIsEmpty_Throws() { - var credentials = MockTokenCredentials( "".ToSecureString()); + var credentials = MockTokenCredentials("".ToSecureString()); var act = () => AuthenticationHeaderFactory.Create(credentials); @@ -157,7 +157,7 @@ public void Create_BearerNotSupported_TokenIsFilled_ReturnsBasicScheme() { var credentials = MockTokenCredentials(Token.ToSecureString()); - var authenticationHeaderValue = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer:false); + var authenticationHeaderValue = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer: false); authenticationHeaderValue.Scheme.Should().Be("Basic"); AssertAreEqualUserNameAndPassword(Token, string.Empty, authenticationHeaderValue.Parameter); @@ -168,12 +168,14 @@ public void Create_BearerNotSupported_TokenIsEmpty_Throws() { var credentials = MockTokenCredentials("".ToSecureString()); - var act = () => AuthenticationHeaderFactory.Create(credentials, shouldUseBearer:false); + var act = () => AuthenticationHeaderFactory.Create(credentials, shouldUseBearer: false); act.Should().Throw(); } - private static void AssertAreEqualUserNameAndPassword(string expectedUser, string expectedPassword, + private static void AssertAreEqualUserNameAndPassword( + string expectedUser, + string expectedPassword, string userAndPasswordBase64String) { diff --git a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs index 73ae1a2477..f5f960c010 100644 --- a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs +++ b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs @@ -41,15 +41,15 @@ public static AuthenticationHeaderValue Create(IConnectionCredentials credential switch (credentials) { case ITokenCredentials tokenCredentials: - { - ValidateSecureString(tokenCredentials.Token, nameof(tokenCredentials.Token)); - return CreateAuthenticationHeaderValueForTokenAuth(shouldUseBearer, tokenCredentials); - } + { + ValidateSecureString(tokenCredentials.Token, nameof(tokenCredentials.Token)); + return CreateAuthenticationHeaderValueForTokenAuth(shouldUseBearer, tokenCredentials); + } case IBasicAuthCredentials basicAuthCredentials: - { - ValidateCredentials(basicAuthCredentials); - return CreateAuthenticationHeaderValueForBasicAuth(basicAuthCredentials.UserName, basicAuthCredentials.Password); - } + { + ValidateCredentials(basicAuthCredentials); + return CreateAuthenticationHeaderValueForBasicAuth(basicAuthCredentials.UserName, basicAuthCredentials.Password); + } case INoCredentials: return null; default: diff --git a/src/SonarQube.Client/SonarQubeService.cs b/src/SonarQube.Client/SonarQubeService.cs index ac0a0e247c..d817747fa5 100644 --- a/src/SonarQube.Client/SonarQubeService.cs +++ b/src/SonarQube.Client/SonarQubeService.cs @@ -585,7 +585,7 @@ private static bool ShouldUseBearer(ServerInfo serverInfo) private async Task GetServerInfo(ConnectionInformation connection, CancellationToken token) { - var http = CreateHttpClient(connection.ServerUri, new NoCredentials(), shouldUseBearer:true); + var http = CreateHttpClient(connection.ServerUri, new NoCredentials(), shouldUseBearer: true); var versionResponse = await InvokeUncheckedRequestAsync(request => { }, http, token); var serverInfo = new ServerInfo(Version.Parse(versionResponse), connection.IsSonarCloud ? ServerType.SonarCloud : ServerType.SonarQube); return serverInfo; @@ -593,14 +593,7 @@ private async Task GetServerInfo(ConnectionInformation connection, C private HttpClient CreateHttpClient(Uri baseAddress, IConnectionCredentials credentials, bool shouldUseBearer) { - var client = new HttpClient(messageHandler) - { - BaseAddress = baseAddress, - DefaultRequestHeaders = - { - Authorization = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer), - }, - }; + var client = new HttpClient(messageHandler) { BaseAddress = baseAddress, DefaultRequestHeaders = { Authorization = AuthenticationHeaderFactory.Create(credentials, shouldUseBearer), }, }; client.DefaultRequestHeaders.Add("User-Agent", userAgent); return client; } From 5e0bd1ec1fbc26e318f43e5fdd57cf55bcb475f1 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 10:27:34 +0100 Subject: [PATCH 5/8] SLVS-1738 Drop redundante interface --- .../BindingJsonModelConverterTests.cs | 2 +- .../ServerConnectionsRepositoryTests.cs | 33 ++++++++++--------- .../SolutionBindingCredentialsLoaderTests.cs | 3 +- ...ServerConnectionsRepositoryAdapterTests.cs | 19 ++++++----- .../ProjectSelectionViewModelTests.cs | 7 ++-- .../Persistence/BasicAuthCredentials.cs | 3 +- .../Persistence/BindingJsonModelConverter.cs | 4 +-- .../ISolutionBindingCredentialsLoader.cs | 7 ++-- .../ServerConnectionsRepository.cs | 3 +- .../SolutionBindingCredentialsLoader.cs | 6 ++-- .../ServerConnectionsRepositoryAdapter.cs | 3 +- src/ConnectedMode/SlCoreConnectionAdapter.cs | 5 +-- .../UI/Credentials/ICredentialsModel.cs | 8 ++--- .../Binding/ServerConnectionTests.cs | 9 +++-- src/Core/Binding/BoundSonarQubeProject.cs | 4 +-- src/Core/Binding/ICredentials.cs | 28 ---------------- .../Binding/IServerConnectionsRepository.cs | 4 ++- src/Core/Binding/ServerConnection.cs | 9 ++--- 18 files changed, 68 insertions(+), 89 deletions(-) delete mode 100644 src/Core/Binding/ICredentials.cs diff --git a/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs b/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs index c329050866..7c62b2842a 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs @@ -111,7 +111,7 @@ public void ConvertToModel_SonarQubeConnection_ConvertsCorrectly() [TestMethod] public void ConvertFromModelToLegacy_ConvertsCorrectly() { - var credentials = Substitute.For(); + var credentials = Substitute.For(); var bindingModel = new BindingJsonModel { Organization = new SonarQubeOrganization("org", "my org"), diff --git a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs index 03e2e2a209..4be7d15872 100644 --- a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs @@ -27,6 +27,7 @@ using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Core.Persistence; using SonarLint.VisualStudio.TestInfrastructure; +using SonarQube.Client.Models; using static SonarLint.VisualStudio.Core.Binding.ServerConnection; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; @@ -40,8 +41,8 @@ public class ServerConnectionsRepositoryTests private IEnvironmentVariableProvider environmentVariableProvider; private IServerConnectionModelMapper serverConnectionModelMapper; private ISolutionBindingCredentialsLoader credentialsLoader; - private readonly SonarCloud sonarCloudServerConnection = new("myOrganization", new ServerConnectionSettings(true), Substitute.For()); - private readonly ServerConnection.SonarQube sonarQubeServerConnection = new(new Uri("http://localhost"), new ServerConnectionSettings(true), Substitute.For()); + private readonly SonarCloud sonarCloudServerConnection = new("myOrganization", new ServerConnectionSettings(true), Substitute.For()); + private readonly ServerConnection.SonarQube sonarQubeServerConnection = new(new Uri("http://localhost"), new ServerConnectionSettings(true), Substitute.For()); private IFileSystem fileSystem; [TestInitialize] @@ -128,7 +129,7 @@ public void TryGet_FileExistsAndConnectionIsSonarCloud_ReturnsSonarCloudConnecti public void TryGet_FileExistsAndConnectionIsSonarCloud_FillsCredentials() { var expectedConnection = MockFileWithOneSonarCloudConnection(); - var credentials = Substitute.For(); + var credentials = Substitute.For(); credentialsLoader.Load(expectedConnection.CredentialsUri).Returns(credentials); var succeeded = testSubject.TryGet(expectedConnection.Id, out ServerConnection serverConnection); @@ -157,7 +158,7 @@ public void TryGet_FileExistsAndConnectionIsSonarQube_ReturnsSonarQubeConnection public void TryGet_FileExistsAndConnectionIsSonarQube_FillsCredentials() { var expectedConnection = MockFileWithOneSonarQubeConnection(); - var credentials = Substitute.For(); + var credentials = Substitute.For(); credentialsLoader.Load(expectedConnection.CredentialsUri).Returns(credentials); var succeeded = testSubject.TryGet(expectedConnection.Id, out ServerConnection serverConnection); @@ -289,7 +290,7 @@ public void TryAdd_ConnectionIsAddedAndCredentialsAreNull_ReturnsFalse() var succeeded = testSubject.TryAdd(sonarCloudServerConnection); succeeded.Should().BeFalse(); - credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); + credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); } [TestMethod] @@ -300,7 +301,7 @@ public void TryAdd_ConnectionIsNotAdded_DoesNotSaveCredentials() var succeeded = testSubject.TryAdd(sonarCloud); succeeded.Should().BeFalse(); - credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); + credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); } [TestMethod] @@ -589,17 +590,17 @@ public void TryUpdateCredentialsById_ConnectionDoesNotExist_DoesNotUpdateCredent { MockReadingFile(new ServerConnectionsListJsonModel()); - var succeeded = testSubject.TryUpdateCredentialsById("myConn", Substitute.For()); + var succeeded = testSubject.TryUpdateCredentialsById("myConn", Substitute.For()); succeeded.Should().BeFalse(); - credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); + credentialsLoader.DidNotReceive().Save(Arg.Any(), Arg.Any()); } [TestMethod] public void TryUpdateCredentialsById_SonarCloudConnectionExists_UpdatesCredentials() { var sonarCloud = MockFileWithOneSonarCloudConnection(); - var newCredentials = Substitute.For(); + var newCredentials = Substitute.For(); var succeeded = testSubject.TryUpdateCredentialsById(sonarCloud.Id, newCredentials); @@ -611,7 +612,7 @@ public void TryUpdateCredentialsById_SonarCloudConnectionExists_UpdatesCredentia public void TryUpdateCredentialsById_SonarQubeConnectionExists_UpdatesCredentials() { var sonarQube = MockFileWithOneSonarQubeConnection(); - var newCredentials = Substitute.For(); + var newCredentials = Substitute.For(); var succeeded = testSubject.TryUpdateCredentialsById(sonarQube.Id, newCredentials); @@ -626,7 +627,7 @@ public void TryUpdateCredentialsById_DoesNotUpdateCredentials_DoesNotInvokeConne var eventHandler = Substitute.For>(); testSubject.CredentialsChanged += eventHandler; - testSubject.TryUpdateCredentialsById("non-existingConn", Substitute.For()); + testSubject.TryUpdateCredentialsById("non-existingConn", Substitute.For()); eventHandler.DidNotReceive().Invoke(testSubject, Arg.Any()); } @@ -638,7 +639,7 @@ public void TryUpdateCredentialsById_UpdatesCredentials_InvokesConnectionChanged var eventHandler = Substitute.For>(); testSubject.CredentialsChanged += eventHandler; - testSubject.TryUpdateCredentialsById(sonarQube.Id, Substitute.For()); + testSubject.TryUpdateCredentialsById(sonarQube.Id, Substitute.For()); eventHandler.Received(1).Invoke(testSubject, Arg.Is(args => args.ServerConnection == sonarQube)); } @@ -660,9 +661,9 @@ public void TryUpdateCredentialsById_SavingCredentialsThrows_ReturnsFalseAndLogs { var exceptionMsg = "failed"; var connection = MockFileWithOneSonarCloudConnection(); - credentialsLoader.When(x => x.Save(Arg.Any(), Arg.Any())).Do(x => throw new Exception(exceptionMsg)); + credentialsLoader.When(x => x.Save(Arg.Any(), Arg.Any())).Do(x => throw new Exception(exceptionMsg)); - var succeeded = testSubject.TryUpdateCredentialsById(connection.Id, Substitute.For()); + var succeeded = testSubject.TryUpdateCredentialsById(connection.Id, Substitute.For()); succeeded.Should().BeFalse(); logger.Received(1).WriteLine($"Failed updating credentials: {exceptionMsg}"); @@ -671,7 +672,7 @@ public void TryUpdateCredentialsById_SavingCredentialsThrows_ReturnsFalseAndLogs private SonarCloud MockFileWithOneSonarCloudConnection(bool isSmartNotificationsEnabled = true) { var sonarCloudModel = GetSonarCloudJsonModel(isSmartNotificationsEnabled); - var sonarCloud = new SonarCloud(sonarCloudModel.OrganizationKey, sonarCloudModel.Settings, Substitute.For()); + var sonarCloud = new SonarCloud(sonarCloudModel.OrganizationKey, sonarCloudModel.Settings, Substitute.For()); MockReadingFile(new ServerConnectionsListJsonModel { ServerConnections = [sonarCloudModel] }); serverConnectionModelMapper.GetServerConnection(sonarCloudModel).Returns(sonarCloud); @@ -681,7 +682,7 @@ private SonarCloud MockFileWithOneSonarCloudConnection(bool isSmartNotifications private ServerConnection.SonarQube MockFileWithOneSonarQubeConnection(bool isSmartNotificationsEnabled = true) { var sonarQubeModel = GetSonarQubeJsonModel(new Uri("http://localhost"), isSmartNotificationsEnabled); - var sonarQube = new ServerConnection.SonarQube(new Uri(sonarQubeModel.ServerUri), sonarQubeModel.Settings, Substitute.For()); + var sonarQube = new ServerConnection.SonarQube(new Uri(sonarQubeModel.ServerUri), sonarQubeModel.Settings, Substitute.For()); MockReadingFile(new ServerConnectionsListJsonModel { ServerConnections = [sonarQubeModel] }); serverConnectionModelMapper.GetServerConnection(sonarQubeModel).Returns(sonarQube); diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs index d92a4bd945..cb56fb70e3 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs @@ -23,6 +23,7 @@ using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence { @@ -98,7 +99,7 @@ public void Save_CredentialsAreNull_CredentialsNotSaved() [TestMethod] public void Save_CredentialsAreNotBasicAuth_CredentialsNotSaved() { - var mockCredentials = new Mock(); + var mockCredentials = new Mock(); testSubject.Save(mockCredentials.Object, mockUri); store.DidNotReceive().WriteCredentials(Arg.Any(), Arg.Any()); diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs index 363a1798af..53e2a4e988 100644 --- a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -23,6 +23,7 @@ using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; using static SonarLint.VisualStudio.Core.Binding.ServerConnection; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; @@ -241,7 +242,7 @@ public void TryAddConnection_NullCredentials_TriesAddingAConnectionWithNoCredent public void TryUpdateCredentials_ReturnsStatusFromSlCore(bool slCoreResponse) { var sonarCloud = CreateSonarCloudConnection(); - serverConnectionsRepository.TryUpdateCredentialsById(Arg.Any(), Arg.Any()).Returns(slCoreResponse); + serverConnectionsRepository.TryUpdateCredentialsById(Arg.Any(), Arg.Any()).Returns(slCoreResponse); var succeeded = testSubject.TryUpdateCredentials(sonarCloud, Substitute.For()); @@ -257,7 +258,7 @@ public void TryUpdateCredentials_TokenCredentialsModel_MapsCredentials() testSubject.TryUpdateCredentials(sonarQube, new TokenCredentialsModel(token.CreateSecureString())); serverConnectionsRepository.Received(1) - .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, token, string.Empty))); + .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, token, string.Empty))); } [TestMethod] @@ -270,7 +271,7 @@ public void TryUpdateCredentials_UserPasswordModel_MapsCredentials() testSubject.TryUpdateCredentials(sonarQube, new UsernamePasswordModel(username, password.CreateSecureString())); serverConnectionsRepository.Received(1) - .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, username, password))); + .TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => IsExpectedCredentials(x, username, password))); } [TestMethod] @@ -281,7 +282,7 @@ public void TryUpdateCredentials_SonarQube_MapsConnection() testSubject.TryUpdateCredentials(sonarQube, Substitute.For()); serverConnectionsRepository.Received(1) - .TryUpdateCredentialsById(Arg.Is(x => x.Equals(sonarQube.Info.Id)), Arg.Any()); + .TryUpdateCredentialsById(Arg.Is(x => x.Equals(sonarQube.Info.Id)), Arg.Any()); } [TestMethod] @@ -292,7 +293,7 @@ public void TryUpdateCredentials_SonarCloud_MapsConnection() testSubject.TryUpdateCredentials(sonarCloud, Substitute.For()); serverConnectionsRepository.Received(1) - .TryUpdateCredentialsById(Arg.Is(x => x.EndsWith(sonarCloud.Info.Id)), Arg.Any()); + .TryUpdateCredentialsById(Arg.Is(x => x.EndsWith(sonarCloud.Info.Id)), Arg.Any()); } [TestMethod] @@ -302,7 +303,7 @@ public void TryUpdateCredentials_NullCredentials_TriesUpdatingConnectionWithNoCr testSubject.TryUpdateCredentials(sonarQube, null); - serverConnectionsRepository.Received(1).TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => x == null)); + serverConnectionsRepository.Received(1).TryUpdateCredentialsById(Arg.Any(), Arg.Is(x => x == null)); } [TestMethod] @@ -337,12 +338,12 @@ public void TryGet_ReturnsStatusFromSlCore(bool expectedStatus) private static SonarCloud CreateSonarCloudServerConnection(bool isSmartNotificationsEnabled = true) { - return new SonarCloud("myOrg", new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); + return new SonarCloud("myOrg", new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); } private static ServerConnection.SonarQube CreateSonarQubeServerConnection(bool isSmartNotificationsEnabled = true) { - var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost"), new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); + var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost"), new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); return sonarQube; } @@ -365,7 +366,7 @@ private static Connection CreateSonarQubeConnection(bool enableSmartNotification return new Connection(new ConnectionInfo("http://localhost:9000/", ConnectionServerType.SonarQube), enableSmartNotifications); } - private static bool IsExpectedCredentials(ICredentials credentials, string expectedUsername, string expectedPassword) + private static bool IsExpectedCredentials(IConnectionCredentials credentials, string expectedUsername, string expectedPassword) { return credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; } diff --git a/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs index 304743c649..1822cfe8e9 100644 --- a/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs @@ -24,6 +24,7 @@ using SonarLint.VisualStudio.ConnectedMode.UI.Resources; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI.ProjectSelection; @@ -229,7 +230,7 @@ public async Task InitializeProjectWithProgressAsync_OnFailure_InitialServerProj [TestMethod] public async Task AdapterGetAllProjectsAsync_GettingServerConnectionSucceeded_CallsAdapterWithCredentialsForServerConnection() { - var expectedCredentials = Substitute.For(); + var expectedCredentials = Substitute.For(); MockTrySonarQubeConnection(AConnectionInfo, success:true, expectedCredentials); await testSubject.AdapterGetAllProjectsAsync(); @@ -241,7 +242,7 @@ public async Task AdapterGetAllProjectsAsync_GettingServerConnectionSucceeded_Ca [TestMethod] public async Task AdapterGetAllProjectsAsync_GettingServerConnectionSucceeded_StoresServerConnection() { - MockTrySonarQubeConnection(AConnectionInfo, success: true, Substitute.For()); + MockTrySonarQubeConnection(AConnectionInfo, success: true, Substitute.For()); await testSubject.AdapterGetAllProjectsAsync(); @@ -304,7 +305,7 @@ private void MockInitializedProjects(List serverProjects) testSubject.InitProjects(new AdapterResponseWithData>(true, serverProjects)); } - private void MockTrySonarQubeConnection(ConnectionInfo connectionInfo, bool success = true, ICredentials expectedCredentials = null) + private void MockTrySonarQubeConnection(ConnectionInfo connectionInfo, bool success = true, IConnectionCredentials expectedCredentials = null) { serverConnectionsRepositoryAdapter.TryGet(connectionInfo, out _).Returns(callInfo => { diff --git a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs b/src/ConnectedMode/Persistence/BasicAuthCredentials.cs index 927af48a6a..adea8f030c 100644 --- a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs +++ b/src/ConnectedMode/Persistence/BasicAuthCredentials.cs @@ -19,13 +19,12 @@ */ using System.Security; -using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Helpers; using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.Persistence; -internal sealed class BasicAuthCredentials(string userName, SecureString password) : ICredentials, IBasicAuthCredentials +internal sealed class BasicAuthCredentials(string userName, SecureString password) : IBasicAuthCredentials { public string UserName { get; } = userName ?? throw new ArgumentNullException(nameof(userName)); diff --git a/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs b/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs index 763724a04b..63b243b54a 100644 --- a/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs +++ b/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs @@ -28,7 +28,7 @@ internal interface IBindingJsonModelConverter { BoundServerProject ConvertFromModel(BindingJsonModel bindingJsonModel, ServerConnection connection, string localBindingKey); BindingJsonModel ConvertToModel(BoundServerProject binding); - BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, ICredentials credentials); + BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, IConnectionCredentials credentials); } [Export(typeof(IBindingJsonModelConverter))] @@ -54,7 +54,7 @@ public BindingJsonModel ConvertToModel(BoundServerProject binding) => : null }; - public BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, ICredentials credentials) => + public BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, IConnectionCredentials credentials) => new(bindingJsonModel.ServerUri, bindingJsonModel.ProjectKey, bindingJsonModel.ProjectName, diff --git a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs index d3bfb0c31b..a26366d85b 100644 --- a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs @@ -18,15 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.Persistence { interface ISolutionBindingCredentialsLoader { void DeleteCredentials(Uri boundServerUri); - ICredentials Load(Uri boundServerUri); - void Save(ICredentials credentials, Uri boundServerUri); + IConnectionCredentials Load(Uri boundServerUri); + void Save(IConnectionCredentials credentials, Uri boundServerUri); } } diff --git a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs index 7554307cfc..333f9da328 100644 --- a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs +++ b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs @@ -25,6 +25,7 @@ using System.IO.Abstractions; using SonarLint.VisualStudio.Core.Persistence; using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.Persistence; @@ -122,7 +123,7 @@ public bool TryUpdateSettingsById(string connectionId, ServerConnectionSettings return SafeUpdateConnectionsFile(connections => TryUpdateConnectionSettings(connections, connectionId, connectionSettings)); } - public bool TryUpdateCredentialsById(string connectionId, ICredentials credentials) + public bool TryUpdateCredentialsById(string connectionId, IConnectionCredentials credentials) { try { diff --git a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs index 6232873efd..dd347df442 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs @@ -20,8 +20,8 @@ using Microsoft.Alm.Authentication; using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.Persistence { @@ -43,7 +43,7 @@ public void DeleteCredentials(Uri boundServerUri) store.DeleteCredentials(boundServerUri); } - public ICredentials Load(Uri boundServerUri) + public IConnectionCredentials Load(Uri boundServerUri) { if (boundServerUri == null) { @@ -61,7 +61,7 @@ public ICredentials Load(Uri boundServerUri) Justification = "Casting as BasicAuthCredentials is because it's the only credential type we support. Once we add more we need to think again on how to refactor the code to avoid this", Scope = "member", Target = "~M:SonarLint.VisualStudio.Integration.Persistence.FileBindingSerializer.WriteBindingInformation(System.String,SonarLint.VisualStudio.Integration.Persistence.BoundProject)~System.Boolean")] - public void Save(ICredentials credentials, Uri boundServerUri) + public void Save(IConnectionCredentials credentials, Uri boundServerUri) { if (boundServerUri == null || !(credentials is BasicAuthCredentials basicCredentials)) { diff --git a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs index 5e684787ea..132548d431 100644 --- a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs +++ b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs @@ -24,6 +24,7 @@ using SonarLint.VisualStudio.ConnectedMode.UI.Credentials; using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode; @@ -97,7 +98,7 @@ private static ServerConnection MapConnection(Connection connection) return new ServerConnection.SonarQube(new Uri(connection.Info.Id), new ServerConnectionSettings(connection.EnableSmartNotifications)); } - private static ICredentials MapCredentials(ICredentialsModel credentialsModel) + private static IConnectionCredentials MapCredentials(ICredentialsModel credentialsModel) { switch (credentialsModel) { diff --git a/src/ConnectedMode/SlCoreConnectionAdapter.cs b/src/ConnectedMode/SlCoreConnectionAdapter.cs index 169433bd83..febbe3a36a 100644 --- a/src/ConnectedMode/SlCoreConnectionAdapter.cs +++ b/src/ConnectedMode/SlCoreConnectionAdapter.cs @@ -33,6 +33,7 @@ using SonarLint.VisualStudio.SLCore.Service.Connection; using SonarLint.VisualStudio.SLCore.Service.Connection.Models; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode; @@ -230,7 +231,7 @@ private bool TryGetConnectionConfigurationSlCoreService(out IConnectionConfigura return false; } - private static Either GetTransientConnectionDto(ConnectionInfo connectionInfo, ICredentials credentials) + private static Either GetTransientConnectionDto(ConnectionInfo connectionInfo, IConnectionCredentials credentials) { var credentialsDto = MapCredentials(credentials); @@ -268,7 +269,7 @@ private static Either GetEitherForUsernamePasswor return Either.CreateRight(new UsernamePasswordDto(username, password)); } - private static Either MapCredentials(ICredentials credentials) + private static Either MapCredentials(IConnectionCredentials credentials) { if (credentials == null) { diff --git a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs index 2e14786ec5..bbfdf3fa6a 100644 --- a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs +++ b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs @@ -20,21 +20,21 @@ using System.Security; using SonarLint.VisualStudio.ConnectedMode.Persistence; -using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.UI.Credentials; public interface ICredentialsModel { - ICredentials ToICredentials(); + IConnectionCredentials ToICredentials(); } public class TokenCredentialsModel(SecureString token) : ICredentialsModel { public SecureString Token { get; } = token; - public ICredentials ToICredentials() + public IConnectionCredentials ToICredentials() { return new BasicAuthCredentials(Token.ToUnsecureString(), new SecureString()); } @@ -45,7 +45,7 @@ public class UsernamePasswordModel(string username, SecureString password) : ICr public string Username { get; } = username; public SecureString Password { get; } = password; - public ICredentials ToICredentials() + public IConnectionCredentials ToICredentials() { return new BasicAuthCredentials(Username, Password); } diff --git a/src/Core.UnitTests/Binding/ServerConnectionTests.cs b/src/Core.UnitTests/Binding/ServerConnectionTests.cs index af266ddcec..39842c4323 100644 --- a/src/Core.UnitTests/Binding/ServerConnectionTests.cs +++ b/src/Core.UnitTests/Binding/ServerConnectionTests.cs @@ -20,7 +20,6 @@ using SonarLint.VisualStudio.Core.Binding; using SonarQube.Client.Models; -using ICredentials = SonarLint.VisualStudio.Core.Binding.ICredentials; namespace SonarLint.VisualStudio.Core.UnitTests.Binding; @@ -58,7 +57,7 @@ public void Ctor_SonarCloud_NullCredentials_SetsNull() public void Ctor_SonarCloud_SetsProperties() { var serverConnectionSettings = new ServerConnectionSettings(false); - var credentials = Substitute.For(); + var credentials = Substitute.For(); var sonarCloud = new ServerConnection.SonarCloud(Org, serverConnectionSettings, credentials); sonarCloud.Id.Should().Be($"https://sonarcloud.io/organizations/{Org}"); @@ -97,7 +96,7 @@ public void Ctor_SonarQube_NullCredentials_SetsNull() public void Ctor_SonarQube_SetsProperties() { var serverConnectionSettings = new ServerConnectionSettings(false); - var credentials = Substitute.For(); + var credentials = Substitute.For(); var sonarQube = new ServerConnection.SonarQube(Localhost, serverConnectionSettings, credentials); sonarQube.Id.Should().Be(Localhost.ToString()); @@ -110,7 +109,7 @@ public void Ctor_SonarQube_SetsProperties() [TestMethod] public void FromBoundSonarQubeProject_SonarQubeConnection_ConvertedCorrectly() { - var credentials = Substitute.For(); + var credentials = Substitute.For(); var expectedConnection = new ServerConnection.SonarQube(Localhost, credentials: credentials); var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject(Localhost, "any", "any", credentials)); @@ -123,7 +122,7 @@ public void FromBoundSonarQubeProject_SonarCloudConnection_ConvertedCorrectly() { var uri = new Uri("https://sonarcloud.io"); var organization = "org"; - var credentials = Substitute.For(); + var credentials = Substitute.For(); var expectedConnection = new ServerConnection.SonarCloud(organization, credentials: credentials); var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject(uri, "any", "any", credentials, new SonarQubeOrganization(organization, null))); diff --git a/src/Core/Binding/BoundSonarQubeProject.cs b/src/Core/Binding/BoundSonarQubeProject.cs index e66ea8434d..fdfbae1b42 100644 --- a/src/Core/Binding/BoundSonarQubeProject.cs +++ b/src/Core/Binding/BoundSonarQubeProject.cs @@ -32,7 +32,7 @@ public BoundSonarQubeProject() } public BoundSonarQubeProject(Uri serverUri, string projectKey, string projectName, - ICredentials credentials = null, SonarQubeOrganization organization = null) + IConnectionCredentials credentials = null, SonarQubeOrganization organization = null) : this() { if (serverUri == null) @@ -60,6 +60,6 @@ public BoundSonarQubeProject(Uri serverUri, string projectKey, string projectNam public Dictionary Profiles { get; set; } [JsonIgnore] - public ICredentials Credentials { get; set; } + public IConnectionCredentials Credentials { get; set; } } } diff --git a/src/Core/Binding/ICredentials.cs b/src/Core/Binding/ICredentials.cs deleted file mode 100644 index d25430a114..0000000000 --- a/src/Core/Binding/ICredentials.cs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using SonarQube.Client.Models; - -namespace SonarLint.VisualStudio.Core.Binding -{ - public interface ICredentials : IConnectionCredentials - { - } -} diff --git a/src/Core/Binding/IServerConnectionsRepository.cs b/src/Core/Binding/IServerConnectionsRepository.cs index 22e988257a..21ab89bed8 100644 --- a/src/Core/Binding/IServerConnectionsRepository.cs +++ b/src/Core/Binding/IServerConnectionsRepository.cs @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarQube.Client.Models; + namespace SonarLint.VisualStudio.Core.Binding; public interface IServerConnectionsRepository @@ -27,7 +29,7 @@ public interface IServerConnectionsRepository bool TryAdd(ServerConnection connectionToAdd); bool TryDelete(string connectionId); bool TryUpdateSettingsById(string connectionId, ServerConnectionSettings connectionSettings); - bool TryUpdateCredentialsById(string connectionId, ICredentials credentials); + bool TryUpdateCredentialsById(string connectionId, IConnectionCredentials credentials); bool ConnectionsFileExists(); event EventHandler ConnectionChanged; event EventHandler CredentialsChanged; diff --git a/src/Core/Binding/ServerConnection.cs b/src/Core/Binding/ServerConnection.cs index f4d2f5c2ee..d1e51272d5 100644 --- a/src/Core/Binding/ServerConnection.cs +++ b/src/Core/Binding/ServerConnection.cs @@ -19,6 +19,7 @@ */ using System.IO; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.Core.Binding; @@ -28,7 +29,7 @@ public abstract class ServerConnection public string Id { get; } public ServerConnectionSettings Settings { get; set; } - public ICredentials Credentials { get; set; } + public IConnectionCredentials Credentials { get; set; } public abstract Uri ServerUri { get; } public abstract Uri CredentialsUri { get; } @@ -41,7 +42,7 @@ public static ServerConnection FromBoundSonarQubeProject(BoundSonarQubeProject b _ => null }; - private ServerConnection(string id, ServerConnectionSettings settings = null, ICredentials credentials = null) + private ServerConnection(string id, ServerConnectionSettings settings = null, IConnectionCredentials credentials = null) { Id = id ?? throw new ArgumentNullException(nameof(id)); Settings = settings ?? DefaultSettings; @@ -52,7 +53,7 @@ public sealed class SonarCloud : ServerConnection { private static readonly string SonarCloudUrl = CoreStrings.SonarCloudUrl; - public SonarCloud(string organizationKey, ServerConnectionSettings settings = null, ICredentials credentials = null) + public SonarCloud(string organizationKey, ServerConnectionSettings settings = null, IConnectionCredentials credentials = null) : base(OrganizationKeyToId(organizationKey), settings, credentials) { OrganizationKey = organizationKey; @@ -75,7 +76,7 @@ private static string OrganizationKeyToId(string organizationKey) } } - public sealed class SonarQube(Uri serverUri, ServerConnectionSettings settings = null, ICredentials credentials = null) + public sealed class SonarQube(Uri serverUri, ServerConnectionSettings settings = null, IConnectionCredentials credentials = null) : ServerConnection(serverUri?.ToString(), settings, credentials) { public override Uri ServerUri { get; } = serverUri; From ff1ab40febecb3ced3ac7fa17e2aee684e97f550 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 10:38:26 +0100 Subject: [PATCH 6/8] SLVS-1738 Rename interface --- .../Binding/UnintrusiveBindingControllerTests.cs | 6 +++--- .../Migration/BindingToConnectionMigrationTests.cs | 2 +- .../BoundSonarQubeProjectExtensionsTests.cs | 8 ++++---- .../Persistence/BoundSonarQubeProjectTests.cs | 4 ++-- .../SolutionBindingCredentialsLoaderTests.cs | 6 +++--- .../Persistence/SolutionBindingRepositoryTests.cs | 4 ++-- ...s.cs => UsernameAndPasswordCredentialsTests.cs} | 12 ++++++------ .../ServerConnectionsRepositoryAdapterTests.cs | 2 +- .../SlCoreConnectionAdapterTests.cs | 6 +++--- .../ManageBinding/ManageBindingViewModelTests.cs | 2 +- .../SolutionBindingCredentialsLoader.cs | 4 ++-- ...ntials.cs => UsernameAndPasswordCredentials.cs} | 4 ++-- .../ServerConnectionsRepositoryAdapter.cs | 4 ++-- src/ConnectedMode/SlCoreConnectionAdapter.cs | 2 +- .../UI/Credentials/ICredentialsModel.cs | 4 ++-- .../Service/ConnectionInformationTests.cs | 14 +++++++------- .../CallRealServerTestHarness.cs | 4 ++-- .../Helpers/AuthenticationHeaderFactoryTests.cs | 4 ++-- .../Models/ConnectionInformationTests.cs | 8 ++++---- .../SonarQubeService_Lifecycle.cs | 10 +++++----- .../SonarQubeService_TestBase.cs | 4 ++-- .../Helpers/AuthenticationHeaderFactory.cs | 4 ++-- .../Models/IConnectionCredentials.cs | 2 +- 23 files changed, 60 insertions(+), 60 deletions(-) rename src/ConnectedMode.UnitTests/Persistence/{BasicAuthCredentialsTests.cs => UsernameAndPasswordCredentialsTests.cs} (80%) rename src/ConnectedMode/Persistence/{BasicAuthCredentials.cs => UsernameAndPasswordCredentials.cs} (84%) diff --git a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs index 7d30e3699a..ed404f5949 100644 --- a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs @@ -34,7 +34,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding; public class UnintrusiveBindingControllerTests { private static readonly CancellationToken ACancellationToken = CancellationToken.None; - private static readonly BasicAuthCredentials ValidToken = new("TOKEN", new SecureString()); + private static readonly UsernameAndPasswordCredentials ValidToken = new("TOKEN", new SecureString()); private static readonly BoundServerProject AnyBoundProject = new("any", "any", new ServerConnection.SonarCloud("any", credentials: ValidToken)); private IActiveSolutionChangedHandler activeSolutionChangedHandler; private IBindingProcess bindingProcess; @@ -86,8 +86,8 @@ await sonarQubeService .Received() .ConnectAsync( Arg.Is(x => x.ServerUri.Equals("https://sonarcloud.io/") - && ((BasicAuthCredentials)x.Credentials).UserName.Equals(ValidToken.UserName) - && string.IsNullOrEmpty(((BasicAuthCredentials)x.Credentials).Password.ToUnsecureString())), + && ((UsernameAndPasswordCredentials)x.Credentials).UserName.Equals(ValidToken.UserName) + && string.IsNullOrEmpty(((UsernameAndPasswordCredentials)x.Credentials).Password.ToUnsecureString())), ACancellationToken); } diff --git a/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs b/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs index 1ff381840a..f2dfe1158f 100644 --- a/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs +++ b/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs @@ -234,7 +234,7 @@ private void MockValidBinding(string bindingPath, BoundSonarQubeProject sonarQub private static BoundSonarQubeProject CreateBoundProject(string url, string projectKey) { - return new BoundSonarQubeProject(new Uri(url), projectKey, "projectName", credentials: new BasicAuthCredentials("admin", "admin".ToSecureString())); + return new BoundSonarQubeProject(new Uri(url), projectKey, "projectName", credentials: new UsernameAndPasswordCredentials("admin", "admin".ToSecureString())); } private static bool IsExpectedServerConnection(ServerConnection serverConnection, BoundSonarQubeProject boundProject) diff --git a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs index dfcd94f9a0..a37394851e 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectExtensionsTests.cs @@ -57,7 +57,7 @@ public void BoundSonarQubeProject_CreateConnectionInformation_NoCredentials() public void BoundSonarQubeProject_CreateConnectionInformation_BasicAuthCredentials() { // Arrange - var creds = new BasicAuthCredentials("UserName", "password".ToSecureString()); + var creds = new UsernameAndPasswordCredentials("UserName", "password".ToSecureString()); var input = new BoundSonarQubeProject(new Uri("http://server"), "ProjectKey", "projectName", creds, new SonarQubeOrganization("org_key", "org_name")); @@ -66,7 +66,7 @@ public void BoundSonarQubeProject_CreateConnectionInformation_BasicAuthCredentia // Assert conn.ServerUri.Should().Be(input.ServerUri); - var basicAuth = conn.Credentials as BasicAuthCredentials; + var basicAuth = conn.Credentials as UsernameAndPasswordCredentials; basicAuth.Should().NotBeNull(); basicAuth.UserName.Should().Be(creds.UserName); basicAuth.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); @@ -114,7 +114,7 @@ public void BoundServerProject_CreateConnectionInformation_NoCredentials() public void BoundServerProject_CreateConnectionInformation_BasicAuthCredentials() { // Arrange - var creds = new BasicAuthCredentials("UserName", "password".ToSecureString()); + var creds = new UsernameAndPasswordCredentials("UserName", "password".ToSecureString()); var input = new BoundServerProject("solution", "ProjectKey", new ServerConnection.SonarCloud("org_key", credentials: creds)); // Act @@ -122,7 +122,7 @@ public void BoundServerProject_CreateConnectionInformation_BasicAuthCredentials( // Assert conn.ServerUri.Should().Be(input.ServerConnection.ServerUri); - var basicAuth = conn.Credentials as BasicAuthCredentials; + var basicAuth = conn.Credentials as UsernameAndPasswordCredentials; basicAuth.Should().NotBeNull(); basicAuth.UserName.Should().Be(creds.UserName); basicAuth.Password.ToUnsecureString().Should().Be(creds.Password.ToUnsecureString()); diff --git a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs index 1318f7cdd8..94c81ba94c 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs @@ -36,7 +36,7 @@ public void BoundProject_Serialization() // Arrange var serverUri = new Uri("https://finding-nemo.org"); var projectKey = "MyProject Key"; - var testSubject = new BoundSonarQubeProject(serverUri, projectKey, "projectName", new BasicAuthCredentials("used", "pwd".ToSecureString())); + var testSubject = new BoundSonarQubeProject(serverUri, projectKey, "projectName", new UsernameAndPasswordCredentials("used", "pwd".ToSecureString())); // Act (serialize + de-serialize) string data = JsonHelper.Serialize(testSubject); @@ -55,7 +55,7 @@ public void BoundProject_BindingJsonModel_Serialization() // Arrange var serverUri = new Uri("https://finding-nemo.org"); var projectKey = "MyProject Key"; - var testSubject = new BoundSonarQubeProject(serverUri, projectKey, "projectName", new BasicAuthCredentials("used", "pwd".ToSecureString())); + var testSubject = new BoundSonarQubeProject(serverUri, projectKey, "projectName", new UsernameAndPasswordCredentials("used", "pwd".ToSecureString())); // Act (serialize + de-serialize) string data = JsonHelper.Serialize(testSubject); diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs index cb56fb70e3..9301207c9b 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs @@ -75,13 +75,13 @@ public void Load_CredentialsExist_CredentialsWithSecuredString() .Returns(credentials); var actual = testSubject.Load(mockUri); - actual.Should().BeEquivalentTo(new BasicAuthCredentials("user", "password".ToSecureString())); + actual.Should().BeEquivalentTo(new UsernameAndPasswordCredentials("user", "password".ToSecureString())); } [TestMethod] public void Save_ServerUriIsNull_CredentialsNotSaved() { - var credentials = new BasicAuthCredentials("user", "password".ToSecureString()); + var credentials = new UsernameAndPasswordCredentials("user", "password".ToSecureString()); testSubject.Save(credentials, null); @@ -108,7 +108,7 @@ public void Save_CredentialsAreNotBasicAuth_CredentialsNotSaved() [TestMethod] public void Save_CredentialsAreBasicAuth_CredentialsSavedWithUnsecuredString() { - var credentials = new BasicAuthCredentials("user", "password".ToSecureString()); + var credentials = new UsernameAndPasswordCredentials("user", "password".ToSecureString()); testSubject.Save(credentials, mockUri); store.Received(1) diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs index 7ad191e611..7fd950779e 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs @@ -40,7 +40,7 @@ public class SolutionBindingRepositoryTests private ISolutionBindingCredentialsLoader credentialsLoader; private TestLogger logger; - private BasicAuthCredentials mockCredentials; + private UsernameAndPasswordCredentials mockCredentials; private ServerConnection serverConnection; private IServerConnectionsRepository serverConnectionsRepository; private ISolutionBindingFileLoader solutionBindingFileLoader; @@ -59,7 +59,7 @@ public void TestInitialize() testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider, bindingJsonModelConverter, serverConnectionsRepository, solutionBindingFileLoader, credentialsLoader, logger); - mockCredentials = new BasicAuthCredentials("user", "pwd".ToSecureString()); + mockCredentials = new UsernameAndPasswordCredentials("user", "pwd".ToSecureString()); serverConnection = new ServerConnection.SonarCloud("org"); boundServerProject = new BoundServerProject("solution.123", "project_123", serverConnection); diff --git a/src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs b/src/ConnectedMode.UnitTests/Persistence/UsernameAndPasswordCredentialsTests.cs similarity index 80% rename from src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs rename to src/ConnectedMode.UnitTests/Persistence/UsernameAndPasswordCredentialsTests.cs index 01fc2d12ea..e573bf78d9 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BasicAuthCredentialsTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/UsernameAndPasswordCredentialsTests.cs @@ -25,7 +25,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; [TestClass] -public class BasicAuthCredentialsTests +public class UsernameAndPasswordCredentialsTests { private const string Username = "username"; private const string Password = "pwd"; @@ -33,7 +33,7 @@ public class BasicAuthCredentialsTests [TestMethod] public void Ctor_WhenUsernameIsNull_ThrowsArgumentNullException() { - Action act = () => new BasicAuthCredentials(null, Password.ToSecureString()); + Action act = () => new UsernameAndPasswordCredentials(null, Password.ToSecureString()); act.Should().Throw(); } @@ -41,7 +41,7 @@ public void Ctor_WhenUsernameIsNull_ThrowsArgumentNullException() [TestMethod] public void Ctor_WhenPasswordIsNull_ThrowsArgumentNullException() { - Action act = () => new BasicAuthCredentials(Username, null); + Action act = () => new UsernameAndPasswordCredentials(Username, null); act.Should().Throw(); } @@ -49,7 +49,7 @@ public void Ctor_WhenPasswordIsNull_ThrowsArgumentNullException() [TestMethod] public void Dispose_DisposesPassword() { - var testSubject = new BasicAuthCredentials(Username, Password.ToSecureString()); + var testSubject = new UsernameAndPasswordCredentials(Username, Password.ToSecureString()); testSubject.Dispose(); @@ -60,9 +60,9 @@ public void Dispose_DisposesPassword() public void Clone_ClonesPassword() { var password = "pwd"; - var testSubject = new BasicAuthCredentials(Username, password.ToSecureString()); + var testSubject = new UsernameAndPasswordCredentials(Username, password.ToSecureString()); - var clone = (BasicAuthCredentials)testSubject.Clone(); + var clone = (UsernameAndPasswordCredentials)testSubject.Clone(); clone.Should().NotBeSameAs(testSubject); clone.Password.Should().NotBeSameAs(testSubject.Password); diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs index 53e2a4e988..1d51f86340 100644 --- a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -368,7 +368,7 @@ private static Connection CreateSonarQubeConnection(bool enableSmartNotification private static bool IsExpectedCredentials(IConnectionCredentials credentials, string expectedUsername, string expectedPassword) { - return credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; + return credentials is UsernameAndPasswordCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; } private void MockTryGet(string connectionId, bool expectedResponse, ServerConnection expectedServerConnection) diff --git a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs index d6c2e72896..c3f659fbb6 100644 --- a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs @@ -38,7 +38,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; [TestClass] public class SlCoreConnectionAdapterTests { - private static readonly BasicAuthCredentials ValidToken = new ("I_AM_JUST_A_TOKEN", new SecureString()); + private static readonly UsernameAndPasswordCredentials ValidToken = new ("I_AM_JUST_A_TOKEN", new SecureString()); private readonly ServerConnection.SonarQube sonarQubeConnection = new(new Uri("http://localhost:9000/"), new ServerConnectionSettings(true), ValidToken); private readonly ServerConnection.SonarCloud sonarCloudConnection = new("myOrg", new ServerConnectionSettings(true), ValidToken); @@ -288,7 +288,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarQubeWithCredentials_Calls { const string username = "username"; const string password = "password"; - sonarQubeConnection.Credentials = new BasicAuthCredentials(username, password.CreateSecureString()); + sonarQubeConnection.Credentials = new UsernameAndPasswordCredentials(username, password.CreateSecureString()); await testSubject.GetAllProjectsAsync(sonarQubeConnection); @@ -310,7 +310,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarCloudWithCredentials_Call { const string username = "username"; const string password = "password"; - sonarCloudConnection.Credentials = new BasicAuthCredentials(username, password.CreateSecureString()); + sonarCloudConnection.Credentials = new UsernameAndPasswordCredentials(username, password.CreateSecureString()); await testSubject.GetAllProjectsAsync(sonarCloudConnection); diff --git a/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs index a3ece5229c..91e676ba95 100644 --- a/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs @@ -44,7 +44,7 @@ public class ManageBindingViewModelTests private readonly ServerProject serverProject = new("a-project", "A Project"); private readonly ConnectionInfo sonarQubeConnectionInfo = new("http://localhost:9000", ConnectionServerType.SonarQube); private readonly ConnectionInfo sonarCloudConnectionInfo = new("organization", ConnectionServerType.SonarCloud); - private readonly BasicAuthCredentials validCredentials = new("TOKEN", new SecureString()); + private readonly UsernameAndPasswordCredentials validCredentials = new("TOKEN", new SecureString()); private readonly SharedBindingConfigModel sonarQubeSharedBindingConfigModel = new() { Uri = new Uri("http://localhost:9000"), ProjectKey = "myProj" }; private readonly SharedBindingConfigModel sonarCloudSharedBindingConfigModel = new() { Organization = "myOrg", ProjectKey = "myProj" }; diff --git a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs index dd347df442..4eebb3ead9 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingCredentialsLoader.cs @@ -53,7 +53,7 @@ public IConnectionCredentials Load(Uri boundServerUri) return credentials == null ? null - : new BasicAuthCredentials(credentials.Username, credentials.Password.ToSecureString()); + : new UsernameAndPasswordCredentials(credentials.Username, credentials.Password.ToSecureString()); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", @@ -63,7 +63,7 @@ public IConnectionCredentials Load(Uri boundServerUri) Target = "~M:SonarLint.VisualStudio.Integration.Persistence.FileBindingSerializer.WriteBindingInformation(System.String,SonarLint.VisualStudio.Integration.Persistence.BoundProject)~System.Boolean")] public void Save(IConnectionCredentials credentials, Uri boundServerUri) { - if (boundServerUri == null || !(credentials is BasicAuthCredentials basicCredentials)) + if (boundServerUri == null || !(credentials is UsernameAndPasswordCredentials basicCredentials)) { return; } diff --git a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs b/src/ConnectedMode/Persistence/UsernameAndPasswordCredentials.cs similarity index 84% rename from src/ConnectedMode/Persistence/BasicAuthCredentials.cs rename to src/ConnectedMode/Persistence/UsernameAndPasswordCredentials.cs index adea8f030c..7cc50a2581 100644 --- a/src/ConnectedMode/Persistence/BasicAuthCredentials.cs +++ b/src/ConnectedMode/Persistence/UsernameAndPasswordCredentials.cs @@ -24,7 +24,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence; -internal sealed class BasicAuthCredentials(string userName, SecureString password) : IBasicAuthCredentials +internal sealed class UsernameAndPasswordCredentials(string userName, SecureString password) : IUsernameAndPasswordCredentials { public string UserName { get; } = userName ?? throw new ArgumentNullException(nameof(userName)); @@ -32,5 +32,5 @@ internal sealed class BasicAuthCredentials(string userName, SecureString passwor public void Dispose() => Password?.Dispose(); - public object Clone() => new BasicAuthCredentials(UserName, Password.CopyAsReadOnly()); + public object Clone() => new UsernameAndPasswordCredentials(UserName, Password.CopyAsReadOnly()); } diff --git a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs index 132548d431..5338a7f4c2 100644 --- a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs +++ b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs @@ -103,9 +103,9 @@ private static IConnectionCredentials MapCredentials(ICredentialsModel credentia switch (credentialsModel) { case TokenCredentialsModel tokenCredentialsModel: - return new BasicAuthCredentials(tokenCredentialsModel.Token.ToUnsecureString(), new SecureString()); + return new UsernameAndPasswordCredentials(tokenCredentialsModel.Token.ToUnsecureString(), new SecureString()); case UsernamePasswordModel usernameCredentialsModel: - return new BasicAuthCredentials(usernameCredentialsModel.Username, usernameCredentialsModel.Password); + return new UsernameAndPasswordCredentials(usernameCredentialsModel.Username, usernameCredentialsModel.Password); default: return null; } diff --git a/src/ConnectedMode/SlCoreConnectionAdapter.cs b/src/ConnectedMode/SlCoreConnectionAdapter.cs index febbe3a36a..523f794e2b 100644 --- a/src/ConnectedMode/SlCoreConnectionAdapter.cs +++ b/src/ConnectedMode/SlCoreConnectionAdapter.cs @@ -276,7 +276,7 @@ private static Either MapCredentials(IConnectionC throw new ArgumentException($"Unexpected {nameof(ICredentialsModel)} argument"); } - var basicAuthCredentials = (BasicAuthCredentials) credentials; + var basicAuthCredentials = (UsernameAndPasswordCredentials) credentials; return basicAuthCredentials.Password?.Length > 0 ? GetEitherForUsernamePassword(basicAuthCredentials.UserName, basicAuthCredentials.Password.ToUnsecureString()) : GetEitherForToken(basicAuthCredentials.UserName); diff --git a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs index bbfdf3fa6a..faaa880eb4 100644 --- a/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs +++ b/src/ConnectedMode/UI/Credentials/ICredentialsModel.cs @@ -36,7 +36,7 @@ public class TokenCredentialsModel(SecureString token) : ICredentialsModel public IConnectionCredentials ToICredentials() { - return new BasicAuthCredentials(Token.ToUnsecureString(), new SecureString()); + return new UsernameAndPasswordCredentials(Token.ToUnsecureString(), new SecureString()); } } @@ -47,6 +47,6 @@ public class UsernamePasswordModel(string username, SecureString password) : ICr public IConnectionCredentials ToICredentials() { - return new BasicAuthCredentials(Username, Password); + return new UsernameAndPasswordCredentials(Username, Password); } } diff --git a/src/Integration.UnitTests/Service/ConnectionInformationTests.cs b/src/Integration.UnitTests/Service/ConnectionInformationTests.cs index c03032f070..1730e22203 100644 --- a/src/Integration.UnitTests/Service/ConnectionInformationTests.cs +++ b/src/Integration.UnitTests/Service/ConnectionInformationTests.cs @@ -39,15 +39,15 @@ public void ConnectionInformation_WithLoginInformation() var passwordUnsecure = "admin"; var password = passwordUnsecure.ToSecureString(); var serverUri = new Uri("http://localhost/"); - var credentials = new BasicAuthCredentials(userName, password); + var credentials = new UsernameAndPasswordCredentials(userName, password); var testSubject = new ConnectionInformation(serverUri, credentials); // Act password.Dispose(); // Connection information should maintain it's own copy of the password // Assert - ((BasicAuthCredentials)testSubject.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); - ((BasicAuthCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); + ((UsernameAndPasswordCredentials)testSubject.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); + ((UsernameAndPasswordCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); testSubject.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); // Act clone @@ -57,11 +57,11 @@ public void ConnectionInformation_WithLoginInformation() testSubject.Dispose(); // Assert testSubject - Exceptions.Expect(() => ((BasicAuthCredentials)testSubject.Credentials).Password.ToUnsecureString()); + Exceptions.Expect(() => ((UsernameAndPasswordCredentials)testSubject.Credentials).Password.ToUnsecureString()); // Assert testSubject2 - ((BasicAuthCredentials)testSubject2.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); - ((BasicAuthCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); + ((UsernameAndPasswordCredentials)testSubject2.Credentials).Password.ToUnsecureString().Should().Be(passwordUnsecure, "Password doesn't match"); + ((UsernameAndPasswordCredentials)testSubject.Credentials).UserName.Should().Be(userName, "UserName doesn't match"); testSubject2.ServerUri.Should().Be(serverUri, "ServerUri doesn't match"); } @@ -110,7 +110,7 @@ public void ConnectionInformation_Ctor_FixesSonarCloudUri() public void ConnectionInformation_Ctor_ArgChecks() { Exceptions.Expect(() => new ConnectionInformation(null)); - Exceptions.Expect(() => new ConnectionInformation(null, new BasicAuthCredentials("user", "pwd".ToSecureString()))); + Exceptions.Expect(() => new ConnectionInformation(null, new UsernameAndPasswordCredentials("user", "pwd".ToSecureString()))); } } } diff --git a/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs b/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs index d4bc0f4f04..0640c69b7b 100644 --- a/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs +++ b/src/SonarQube.Client.Tests/CallRealServerTestHarness.cs @@ -92,9 +92,9 @@ public async Task Call_Real_SonarCloud() } } - private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + private static IUsernameAndPasswordCredentials MockBasicAuthCredentials(string userName, SecureString password) { - var mock = Substitute.For(); + var mock = Substitute.For(); mock.UserName.Returns(userName); mock.Password.Returns(password); return mock; diff --git a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs index 9653ae7d77..704a7fde30 100644 --- a/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs +++ b/src/SonarQube.Client.Tests/Helpers/AuthenticationHeaderFactoryTests.cs @@ -121,9 +121,9 @@ private void AssertAreEqualUserNameAndPassword( userNameAndPasswordTokens.Should().HaveElementAt(1, expectedPassword); } - private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + private static IUsernameAndPasswordCredentials MockBasicAuthCredentials(string userName, SecureString password) { - var mock = Substitute.For(); + var mock = Substitute.For(); mock.UserName.Returns(userName); mock.Password.Returns(password); return mock; diff --git a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs index 6f4959a007..7a55c077b4 100644 --- a/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs +++ b/src/SonarQube.Client.Tests/Models/ConnectionInformationTests.cs @@ -115,8 +115,8 @@ private static void CheckPropertiesMatch(ConnectionInformation item1, Connection { item1.ServerUri.Should().Be(item2.ServerUri); - var credentials1 = (IBasicAuthCredentials)item1.Credentials; - var credentials2 = (IBasicAuthCredentials)item2.Credentials; + var credentials1 = (IUsernameAndPasswordCredentials)item1.Credentials; + var credentials2 = (IUsernameAndPasswordCredentials)item2.Credentials; credentials1.UserName.Should().Be(credentials2.UserName); item1.Organization.Should().Be(item2.Organization); @@ -133,9 +133,9 @@ private static void CheckPropertiesMatch(ConnectionInformation item1, Connection item1.IsSonarCloud.Should().Be(item2.IsSonarCloud); } - private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + private static IUsernameAndPasswordCredentials MockBasicAuthCredentials(string userName, SecureString password) { - var mock = Substitute.For(); + var mock = Substitute.For(); mock.UserName.Returns(userName); mock.Password.Returns(password); mock.Clone().Returns(mock); diff --git a/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs b/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs index 26687598d6..5045b3bdc5 100644 --- a/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs +++ b/src/SonarQube.Client.Tests/SonarQubeService_Lifecycle.cs @@ -38,7 +38,7 @@ public async Task Connect_To_SonarQube_Valid_Credentials() service.GetServerInfo().Should().BeNull(); await service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); service.IsConnected.Should().BeTrue(); @@ -56,7 +56,7 @@ public async Task Connect_To_SonarQube_Invalid_Credentials() service.GetServerInfo().Should().BeNull(); Func act = () => service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); var ex = await act.Should().ThrowAsync(); @@ -78,7 +78,7 @@ public async Task Connect_ServerIsNotReachable_IsConnectedIsFalse() service.GetServerInfo().Should().BeNull(); Func act = () => service.ConnectAsync( - new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), + new ConnectionInformation(new Uri("http://localhost"), Mock.Of()), CancellationToken.None); var ex = await act.Should().ThrowAsync(); @@ -101,7 +101,7 @@ public async Task Connect_SonarQube_IsSonarCloud_SonarQubeUrl_ReturnsFalse(strin SetupRequest("api/authentication/validate", "{ \"valid\": true }", serverUrl: canonicalUrl); await service.ConnectAsync( - new ConnectionInformation(new Uri(inputUrl), Mock.Of()), + new ConnectionInformation(new Uri(inputUrl), Mock.Of()), CancellationToken.None); service.GetServerInfo().ServerType.Should().Be(ServerType.SonarQube); @@ -121,7 +121,7 @@ public async Task Connect_SonarQube_IsSonarCloud_SonarCloud_ReturnTrue(string in SetupRequest("api/authentication/validate", "{ \"valid\": true }", serverUrl: fixedSonarCloudUrl); await service.ConnectAsync( - new ConnectionInformation(new Uri(inputUrl), Mock.Of()), + new ConnectionInformation(new Uri(inputUrl), Mock.Of()), CancellationToken.None); service.GetServerInfo().ServerType.Should().Be(ServerType.SonarCloud); diff --git a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs index 72a5e02308..9b8034b51f 100644 --- a/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs +++ b/src/SonarQube.Client.Tests/SonarQubeService_TestBase.cs @@ -120,9 +120,9 @@ protected internal virtual SonarQubeService CreateTestSubject() return new SonarQubeService(messageHandler.Object, UserAgent, logger, requestFactorySelector, secondaryIssueHashUpdater.Object, sseStreamFactory.Object); } - private static IBasicAuthCredentials MockBasicAuthCredentials(string userName, SecureString password) + private static IUsernameAndPasswordCredentials MockBasicAuthCredentials(string userName, SecureString password) { - var mock = new Mock(); + var mock = new Mock(); mock.SetupGet(x => x.UserName).Returns(userName); mock.SetupGet(x => x.Password).Returns(password); diff --git a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs index f5637c728a..91b61f06cb 100644 --- a/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs +++ b/src/SonarQube.Client/Helpers/AuthenticationHeaderFactory.cs @@ -36,7 +36,7 @@ public static class AuthenticationHeaderFactory public static AuthenticationHeaderValue Create(IConnectionCredentials credentials) { - if (credentials is IBasicAuthCredentials basicAuthCredentials) + if (credentials is IUsernameAndPasswordCredentials basicAuthCredentials) { ValidateCredentials(basicAuthCredentials); return new AuthenticationHeaderValue("Basic", GetBasicAuthToken(basicAuthCredentials.UserName, basicAuthCredentials.Password)); @@ -63,7 +63,7 @@ internal static string GetBasicAuthToken(string user, SecureString password) user, password.ToUnsecureString()))); } - private static void ValidateCredentials(IBasicAuthCredentials basicAuthCredentials) + private static void ValidateCredentials(IUsernameAndPasswordCredentials basicAuthCredentials) { if (string.IsNullOrEmpty(basicAuthCredentials.UserName)) { diff --git a/src/SonarQube.Client/Models/IConnectionCredentials.cs b/src/SonarQube.Client/Models/IConnectionCredentials.cs index 88cbe512ad..f544a056f3 100644 --- a/src/SonarQube.Client/Models/IConnectionCredentials.cs +++ b/src/SonarQube.Client/Models/IConnectionCredentials.cs @@ -26,7 +26,7 @@ public interface IConnectionCredentials : IDisposable, ICloneable { } -public interface IBasicAuthCredentials : IConnectionCredentials +public interface IUsernameAndPasswordCredentials : IConnectionCredentials { public string UserName { get; } public SecureString Password { get; } From 7e24f09e78f33792e4a453844640e5862aa6da12 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 10:43:53 +0100 Subject: [PATCH 7/8] SLVS-1732 Fix QG --- .../Persistence/SolutionBindingCredentialsLoaderTests.cs | 4 ++-- src/Integration/MefServices/MefSonarQubeService.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs index f4a14f9411..ce18823f31 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingCredentialsLoaderTests.cs @@ -86,7 +86,7 @@ public void Load_CredentialsExist_UsernameIsEmpty_BasicAuthCredentialsWithSecure var actual = testSubject.Load(mockUri); - actual.Should().BeEquivalentTo(new BasicAuthCredentials(String.Empty, "token".ToSecureString())); + actual.Should().BeEquivalentTo(new BasicAuthCredentials(string.Empty, "token".ToSecureString())); } /// @@ -128,7 +128,7 @@ public void Save_CredentialsAreNotBasicAuth_CredentialsNotSaved() { testSubject.Save(new Mock().Object, mockUri); } - catch (Exception _) + catch (Exception) { // ignored } diff --git a/src/Integration/MefServices/MefSonarQubeService.cs b/src/Integration/MefServices/MefSonarQubeService.cs index e10d153b79..5c829a5d59 100644 --- a/src/Integration/MefServices/MefSonarQubeService.cs +++ b/src/Integration/MefServices/MefSonarQubeService.cs @@ -58,11 +58,11 @@ public MefSonarQubeService(ILogger logger) this.threadHandling = threadHandling; } - protected override async Task InvokeUncheckedRequestAsync(Action configure, HttpClient http, CancellationToken token) + protected override async Task InvokeUncheckedRequestAsync(Action configure, HttpClient httpClientParam, CancellationToken token) { CodeMarkers.Instance.WebClientCallStart(typeof(TRequest).Name); - Func> asyncMethod = () => base.InvokeUncheckedRequestAsync(configure, http, token); + Func> asyncMethod = () => base.InvokeUncheckedRequestAsync(configure, httpClientParam, token); var result = await threadHandling.RunOnBackgroundThread(asyncMethod); From 13ba5f62798548e00fe6e058047df68c371bfcab Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 24 Dec 2024 11:39:22 +0100 Subject: [PATCH 8/8] CaYC: cleanup code applied in touched files --- .../BindingToConnectionMigrationTests.cs | 4 +-- .../BindingJsonModelConverterTests.cs | 9 ++---- .../Persistence/BoundSonarQubeProjectTests.cs | 2 +- .../ServerConnectionsRepositoryTests.cs | 26 +++++---------- .../SolutionBindingRepositoryTests.cs | 2 +- ...ServerConnectionsRepositoryAdapterTests.cs | 3 +- .../SlCoreConnectionAdapterTests.cs | 2 +- .../ProjectSelectionViewModelTests.cs | 32 +++++++------------ .../Persistence/BindingJsonModelConverter.cs | 20 +++++------- .../ISolutionBindingCredentialsLoader.cs | 2 ++ .../ServerConnectionsRepository.cs | 10 +++--- src/ConnectedMode/SlCoreConnectionAdapter.cs | 2 +- .../Binding/ServerConnectionTests.cs | 28 ++++++++-------- src/Core/Binding/BoundSonarQubeProject.cs | 8 +++-- .../Binding/IServerConnectionsRepository.cs | 8 ++++- src/Core/Binding/ServerConnection.cs | 12 +++---- 16 files changed, 78 insertions(+), 92 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs b/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs index f2dfe1158f..403b826e0a 100644 --- a/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs +++ b/src/ConnectedMode.UnitTests/Migration/BindingToConnectionMigrationTests.cs @@ -151,7 +151,6 @@ public async Task MigrateBindingToServerConnectionIfNeeded_MigrationIsExecutedFo var boundProjects = bindingPathToBoundProjectDictionary.Values.ToList(); var expectedServerConnectionId = boundProjects[0].ServerUri.ToString(); serverConnectionsRepository.TryGet(expectedServerConnectionId, out _).Returns(true); - await testSubject.MigrateAllBindingsToServerConnectionsIfNeededAsync(); @@ -215,8 +214,7 @@ private Dictionary CreateTwoBindingPathsToMockedB { Dictionary pathToBindings = new() { - {"bindings/proj1/binding.config", CreateBoundProject("http://server1", "proj1")}, - {"bindings/proj2/binding.config", CreateBoundProject("http://server2", "proj2")} + { "bindings/proj1/binding.config", CreateBoundProject("http://server1", "proj1") }, { "bindings/proj2/binding.config", CreateBoundProject("http://server2", "proj2") } }; unintrusiveBindingPathProvider.GetBindingPaths().Returns(pathToBindings.Select(kvp => kvp.Key)); foreach (var kvp in pathToBindings) diff --git a/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs b/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs index 7c62b2842a..e5b3c7be7c 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BindingJsonModelConverterTests.cs @@ -48,7 +48,7 @@ public void MefCtor_CheckIsSingleton() { MefTestHelpers.CheckIsSingletonMefComponent(); } - + [TestMethod] public void ConvertFromModel_ConvertsCorrectly() { @@ -75,10 +75,7 @@ public void ConvertFromModel_ConvertsCorrectly() [TestMethod] public void ConvertToModel_SonarCloudConnection_ConvertsCorrectly() { - var boundServerProject = new BoundServerProject("localBinding", "serverProject", new ServerConnection.SonarCloud("myorg")) - { - Profiles = new Dictionary() - }; + var boundServerProject = new BoundServerProject("localBinding", "serverProject", new ServerConnection.SonarCloud("myorg")) { Profiles = new Dictionary() }; var bindingModel = testSubject.ConvertToModel(boundServerProject); @@ -89,7 +86,7 @@ public void ConvertToModel_SonarCloudConnection_ConvertsCorrectly() bindingModel.ServerConnectionId.Should().BeSameAs(boundServerProject.ServerConnection.Id); bindingModel.Profiles.Should().BeSameAs(boundServerProject.Profiles); } - + [TestMethod] public void ConvertToModel_SonarQubeConnection_ConvertsCorrectly() { diff --git a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs index 94c81ba94c..17170837d8 100644 --- a/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/BoundSonarQubeProjectTests.cs @@ -48,7 +48,7 @@ public void BoundProject_Serialization() deserialized.ServerUri.Should().Be(testSubject.ServerUri); deserialized.Credentials.Should().BeNull(); } - + [TestMethod] public void BoundProject_BindingJsonModel_Serialization() { diff --git a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs index 4be7d15872..e156c5ae7e 100644 --- a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs @@ -47,7 +47,7 @@ public class ServerConnectionsRepositoryTests [TestInitialize] public void TestInitialize() - { + { jsonFileHandler = Substitute.For(); serverConnectionModelMapper = Substitute.For(); credentialsLoader = Substitute.For(); @@ -175,7 +175,7 @@ public void TryGetAll_FileDoesNotExist_ReturnsEmptyList() MockReadingFile(new ServerConnectionsListJsonModel()); jsonFileHandler.When(x => x.ReadFile(Arg.Any())).Do(x => throw new FileNotFoundException()); - testSubject.TryGetAll(out var connections); + testSubject.TryGetAll(out var connections); connections.Should().BeEmpty(); } @@ -197,7 +197,7 @@ public void TryGetAll_FileExistsAndIsEmpty_ReturnsEmptyList() { MockReadingFile(new ServerConnectionsListJsonModel()); - testSubject.TryGetAll(out var connections); + testSubject.TryGetAll(out var connections); connections.Should().BeEmpty(); } @@ -530,7 +530,8 @@ public void TryUpdateSettingsById_FileExistsAndConnectionExists_UpdatesSettings( Received.InOrder(() => { jsonFileHandler.ReadFile(Arg.Any()); - serverConnectionModelMapper.GetServerConnectionsListJsonModel(Arg.Is>(x => x.Count() == 1 && x.Single().Settings.IsSmartNotificationsEnabled == newSmartNotifications)); + serverConnectionModelMapper.GetServerConnectionsListJsonModel(Arg.Is>(x => + x.Count() == 1 && x.Single().Settings.IsSmartNotificationsEnabled == newSmartNotifications)); jsonFileHandler.TryWriteToFile(Arg.Any(), Arg.Any()); }); } @@ -675,7 +676,7 @@ private SonarCloud MockFileWithOneSonarCloudConnection(bool isSmartNotifications var sonarCloud = new SonarCloud(sonarCloudModel.OrganizationKey, sonarCloudModel.Settings, Substitute.For()); MockReadingFile(new ServerConnectionsListJsonModel { ServerConnections = [sonarCloudModel] }); serverConnectionModelMapper.GetServerConnection(sonarCloudModel).Returns(sonarCloud); - + return sonarCloud; } @@ -696,22 +697,11 @@ private void MockReadingFile(ServerConnectionsListJsonModel modelToReturn) private static ServerConnectionJsonModel GetSonarCloudJsonModel(bool isSmartNotificationsEnabled = false) { - return new ServerConnectionJsonModel - { - Id = "https://sonarcloud.io/organizations/myOrg", - OrganizationKey = "myOrg", - Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) - }; + return new ServerConnectionJsonModel { Id = "https://sonarcloud.io/organizations/myOrg", OrganizationKey = "myOrg", Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) }; } private static ServerConnectionJsonModel GetSonarQubeJsonModel(Uri id, bool isSmartNotificationsEnabled = false) { - return new ServerConnectionJsonModel - { - Id = id.ToString(), - ServerUri = id.ToString(), - Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) - }; + return new ServerConnectionJsonModel { Id = id.ToString(), ServerUri = id.ToString(), Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) }; } - } diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs index 7fd950779e..85d2ee84ab 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs @@ -310,7 +310,7 @@ public void DeleteBinding_DirectoryNotDeleted_EventNotTriggered() { var eventHandler = Substitute.For>(); testSubject.BindingDeleted += eventHandler; - MockDeletingBindingDirectory(LocalBindingKey, deleted:false); + MockDeletingBindingDirectory(LocalBindingKey, deleted: false); testSubject.DeleteBinding(LocalBindingKey); diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs index 49d5973c39..6c44c87521 100644 --- a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -368,7 +368,8 @@ private static Connection CreateSonarQubeConnection(bool enableSmartNotification private static bool IsExpectedCredentials(IConnectionCredentials credentials, string expectedUsername, string expectedPassword) { - return credentials is UsernameAndPasswordCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; + return credentials is UsernameAndPasswordCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && + basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword; } private static bool IsExpectedTokenCredentials(IConnectionCredentials credentials, string expectedToken) diff --git a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs index c1c7213943..4b6210376b 100644 --- a/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs +++ b/src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs @@ -289,7 +289,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarQubeWithCredentials_Calls const string username = "username"; const string password = "password"; sonarQubeConnection.Credentials = new UsernameAndPasswordCredentials(username, password.CreateSecureString()); - + await testSubject.GetAllProjectsAsync(sonarQubeConnection); await connectionConfigurationSlCoreService.Received(1) diff --git a/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs index 1822cfe8e9..3512a8cf20 100644 --- a/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ProjectSelection/ProjectSelectionViewModelTests.cs @@ -38,7 +38,7 @@ public class ProjectSelectionViewModelTests ]; private static readonly ConnectionInfo AConnectionInfo = new("http://localhost:9000", ConnectionServerType.SonarQube); - + private ProjectSelectionViewModel testSubject; private ISlCoreConnectionAdapter slCoreConnectionAdapter; private IProgressReporterViewModel progressReporterViewModel; @@ -66,25 +66,22 @@ public void IsProjectSelected_NoProjectSelected_ReturnsFalse() { testSubject.IsProjectSelected.Should().BeFalse(); } - + [TestMethod] public void IsProjectSelected_ProjectSelected_ReturnsTrue() { testSubject.SelectedProject = new ServerProject("a-project", "A Project"); - + testSubject.IsProjectSelected.Should().BeTrue(); } - + [TestMethod] public void InitProjects_ResetsTheProjectResults() { MockInitializedProjects(AnInitialListOfProjects); testSubject.ProjectResults.Should().BeEquivalentTo(AnInitialListOfProjects); - - var updatedListOfProjects = new List - { - new("new-project", "New Project") - }; + + var updatedListOfProjects = new List { new("new-project", "New Project") }; MockInitializedProjects(updatedListOfProjects); testSubject.ProjectResults.Should().BeEquivalentTo(updatedListOfProjects); } @@ -92,12 +89,7 @@ public void InitProjects_ResetsTheProjectResults() [TestMethod] public void InitProjects_SortsTheProjectResultsByName() { - var unsortedListOfProjects = new List - { - new("a-project", "Y Project"), - new("b-project", "X Project"), - new("c-project", "Z Project") - }; + var unsortedListOfProjects = new List { new("a-project", "Y Project"), new("b-project", "X Project"), new("c-project", "Z Project") }; MockInitializedProjects(unsortedListOfProjects); @@ -180,7 +172,7 @@ public void NoProjectExists_NoProjects_ReturnsTrue() [TestMethod] public void NoProjectExists_HasProjects_ReturnsFalse() { - MockInitializedProjects(AnInitialListOfProjects); + MockInitializedProjects(AnInitialListOfProjects); testSubject.NoProjectExists.Should().BeFalse(); } @@ -231,7 +223,7 @@ public async Task InitializeProjectWithProgressAsync_OnFailure_InitialServerProj public async Task AdapterGetAllProjectsAsync_GettingServerConnectionSucceeded_CallsAdapterWithCredentialsForServerConnection() { var expectedCredentials = Substitute.For(); - MockTrySonarQubeConnection(AConnectionInfo, success:true, expectedCredentials); + MockTrySonarQubeConnection(AConnectionInfo, success: true, expectedCredentials); await testSubject.AdapterGetAllProjectsAsync(); @@ -252,7 +244,7 @@ public async Task AdapterGetAllProjectsAsync_GettingServerConnectionSucceeded_St [TestMethod] public async Task AdapterGetAllProjectsAsync_GettingServerConnectionFailed_ReturnsFailure() { - MockTrySonarQubeConnection(AConnectionInfo, success:false); + MockTrySonarQubeConnection(AConnectionInfo, success: false); var response = await testSubject.AdapterGetAllProjectsAsync(); @@ -269,7 +261,7 @@ public async Task AdapterGetAllProjectsAsync_GettingServerConnectionFailed_Retur public async Task AdapterGetAllProjectsAsync_ReturnsResponseFromAdapter(bool expectedResponse) { MockTrySonarQubeConnection(AConnectionInfo, success: true); - var expectedServerProjects = new List{new("proj1", "name1"), new("proj2", "name2") }; + var expectedServerProjects = new List { new("proj1", "name1"), new("proj2", "name2") }; slCoreConnectionAdapter.GetAllProjectsAsync(Arg.Any()) .Returns(new AdapterResponseWithData>(expectedResponse, expectedServerProjects)); @@ -322,7 +314,7 @@ private ProjectSelectionViewModel CreateInitializedTestSubjectWithNotMockedProgr } private ProjectSelectionViewModel CreateTestSubjectWithNotMockedProgress() - { + { return new ProjectSelectionViewModel(AConnectionInfo, connectedModeServices, new ProgressReporterViewModel(logger)); } } diff --git a/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs b/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs index 63b243b54a..24c7fe6e2e 100644 --- a/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs +++ b/src/ConnectedMode/Persistence/BindingJsonModelConverter.cs @@ -27,7 +27,9 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence; internal interface IBindingJsonModelConverter { BoundServerProject ConvertFromModel(BindingJsonModel bindingJsonModel, ServerConnection connection, string localBindingKey); + BindingJsonModel ConvertToModel(BoundServerProject binding); + BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, IConnectionCredentials credentials); } @@ -36,10 +38,7 @@ internal interface IBindingJsonModelConverter internal class BindingJsonModelConverter : IBindingJsonModelConverter { public BoundServerProject ConvertFromModel(BindingJsonModel bindingJsonModel, ServerConnection connection, string localBindingKey) => - new(localBindingKey, bindingJsonModel.ProjectKey, connection) - { - Profiles = bindingJsonModel.Profiles - }; + new(localBindingKey, bindingJsonModel.ProjectKey, connection) { Profiles = bindingJsonModel.Profiles }; public BindingJsonModel ConvertToModel(BoundServerProject binding) => new() @@ -55,12 +54,9 @@ public BindingJsonModel ConvertToModel(BoundServerProject binding) => }; public BoundSonarQubeProject ConvertFromModelToLegacy(BindingJsonModel bindingJsonModel, IConnectionCredentials credentials) => - new(bindingJsonModel.ServerUri, - bindingJsonModel.ProjectKey, - bindingJsonModel.ProjectName, - credentials, - bindingJsonModel.Organization) - { - Profiles = bindingJsonModel.Profiles - }; + new(bindingJsonModel.ServerUri, + bindingJsonModel.ProjectKey, + bindingJsonModel.ProjectName, + credentials, + bindingJsonModel.Organization) { Profiles = bindingJsonModel.Profiles }; } diff --git a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs index a26366d85b..38bd8ab847 100644 --- a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs @@ -25,7 +25,9 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence interface ISolutionBindingCredentialsLoader { void DeleteCredentials(Uri boundServerUri); + IConnectionCredentials Load(Uri boundServerUri); + void Save(IConnectionCredentials credentials, Uri boundServerUri); } } diff --git a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs index 333f9da328..12d671918e 100644 --- a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs +++ b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs @@ -29,7 +29,6 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence; - [Export(typeof(IServerConnectionsRepository))] [PartCreationPolicy(CreationPolicy.Shared)] internal class ServerConnectionsRepository : IServerConnectionsRepository @@ -57,7 +56,9 @@ public ServerConnectionsRepository( new SolutionBindingCredentialsLoader(credentialStoreService), EnvironmentVariableProvider.Instance, new FileSystem(), - logger) { } + logger) + { + } internal /* for testing */ ServerConnectionsRepository( IJsonFileHandler jsonFileHandle, @@ -85,7 +86,6 @@ public bool TryGet(string connectionId, out ServerConnection serverConnection) serverConnection.Credentials = credentialsLoader.Load(serverConnection.CredentialsUri); return true; - } public bool TryGetAll(out IReadOnlyList serverConnections) @@ -172,7 +172,6 @@ private bool TryAddConnection(List connections, ServerConnecti return false; } - private void TryDeleteCredentials(ServerConnection removedConnection) { try @@ -215,7 +214,7 @@ private List ReadServerConnectionsFromFile() // file not existing should not be treated as an error, as it will be created at the first write return []; } - catch(Exception ex) when (!ErrorHandler.IsCriticalException(ex)) + catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) { logger.WriteLine($"Failed reading the {ConnectionsFileName}: {ex.Message}"); } @@ -252,5 +251,6 @@ private bool SafeUpdateConnectionsFile(Func, bool> tryUpd } private void OnConnectionChanged() => ConnectionChanged?.Invoke(this, EventArgs.Empty); + private void OnCredentialsChanged(ServerConnection serverConnection) => CredentialsChanged?.Invoke(this, new ServerConnectionUpdatedEventArgs(serverConnection)); } diff --git a/src/ConnectedMode/SlCoreConnectionAdapter.cs b/src/ConnectedMode/SlCoreConnectionAdapter.cs index f78e538ce1..7b6e438050 100644 --- a/src/ConnectedMode/SlCoreConnectionAdapter.cs +++ b/src/ConnectedMode/SlCoreConnectionAdapter.cs @@ -236,7 +236,7 @@ private bool TryGetConnectionConfigurationSlCoreService(out IConnectionConfigura logger.LogVerbose($"[{nameof(IConnectionConfigurationSLCoreService)}] {SLCoreStrings.ServiceProviderNotInitialized}"); return false; } - + private static Either GetTransientConnectionDto(ConnectionInfo connectionInfo, IConnectionCredentials credentials) { var credentialsDto = MapCredentials(credentials); diff --git a/src/Core.UnitTests/Binding/ServerConnectionTests.cs b/src/Core.UnitTests/Binding/ServerConnectionTests.cs index 39842c4323..9c929c5e1d 100644 --- a/src/Core.UnitTests/Binding/ServerConnectionTests.cs +++ b/src/Core.UnitTests/Binding/ServerConnectionTests.cs @@ -28,7 +28,7 @@ public class ServerConnectionTests { private static readonly Uri Localhost = new("http://localhost:5000"); private const string Org = "myOrg"; - + [TestMethod] public void Ctor_SonarCloud_NullOrganization_Throws() { @@ -36,7 +36,7 @@ public void Ctor_SonarCloud_NullOrganization_Throws() act.Should().Throw(); } - + [TestMethod] public void Ctor_SonarCloud_NullSettings_SetDefault() { @@ -44,7 +44,7 @@ public void Ctor_SonarCloud_NullSettings_SetDefault() sonarCloud.Settings.Should().BeSameAs(ServerConnection.DefaultSettings); } - + [TestMethod] public void Ctor_SonarCloud_NullCredentials_SetsNull() { @@ -52,7 +52,7 @@ public void Ctor_SonarCloud_NullCredentials_SetsNull() sonarCloud.Credentials.Should().BeNull(); } - + [TestMethod] public void Ctor_SonarCloud_SetsProperties() { @@ -67,7 +67,7 @@ public void Ctor_SonarCloud_SetsProperties() sonarCloud.Credentials.Should().BeSameAs(credentials); sonarCloud.CredentialsUri.Should().Be(new Uri($"https://sonarcloud.io/organizations/{Org}")); } - + [TestMethod] public void Ctor_SonarQube_NullUri_Throws() { @@ -75,7 +75,7 @@ public void Ctor_SonarQube_NullUri_Throws() act.Should().Throw(); } - + [TestMethod] public void Ctor_SonarQube_NullSettings_SetDefault() { @@ -83,7 +83,7 @@ public void Ctor_SonarQube_NullSettings_SetDefault() sonarQube.Settings.Should().BeSameAs(ServerConnection.DefaultSettings); } - + [TestMethod] public void Ctor_SonarQube_NullCredentials_SetsNull() { @@ -91,7 +91,7 @@ public void Ctor_SonarQube_NullCredentials_SetsNull() sonarQube.Credentials.Should().BeNull(); } - + [TestMethod] public void Ctor_SonarQube_SetsProperties() { @@ -113,10 +113,10 @@ public void FromBoundSonarQubeProject_SonarQubeConnection_ConvertedCorrectly() var expectedConnection = new ServerConnection.SonarQube(Localhost, credentials: credentials); var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject(Localhost, "any", "any", credentials)); - + connection.Should().BeEquivalentTo(expectedConnection, options => options.ComparingByMembers()); } - + [TestMethod] public void FromBoundSonarQubeProject_SonarCloudConnection_ConvertedCorrectly() { @@ -126,18 +126,18 @@ public void FromBoundSonarQubeProject_SonarCloudConnection_ConvertedCorrectly() var expectedConnection = new ServerConnection.SonarCloud(organization, credentials: credentials); var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject(uri, "any", "any", credentials, new SonarQubeOrganization(organization, null))); - + connection.Should().BeEquivalentTo(expectedConnection, options => options.ComparingByMembers()); } - + [TestMethod] public void FromBoundSonarQubeProject_InvalidConnection_ReturnsNull() { - var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject(){ ProjectKey = "project"}); + var connection = ServerConnection.FromBoundSonarQubeProject(new BoundSonarQubeProject() { ProjectKey = "project" }); connection.Should().BeNull(); } - + [TestMethod] public void FromBoundSonarQubeProject_NullConnection_ReturnsNull() { diff --git a/src/Core/Binding/BoundSonarQubeProject.cs b/src/Core/Binding/BoundSonarQubeProject.cs index fdfbae1b42..09d2b108f2 100644 --- a/src/Core/Binding/BoundSonarQubeProject.cs +++ b/src/Core/Binding/BoundSonarQubeProject.cs @@ -31,8 +31,12 @@ public BoundSonarQubeProject() { } - public BoundSonarQubeProject(Uri serverUri, string projectKey, string projectName, - IConnectionCredentials credentials = null, SonarQubeOrganization organization = null) + public BoundSonarQubeProject( + Uri serverUri, + string projectKey, + string projectName, + IConnectionCredentials credentials = null, + SonarQubeOrganization organization = null) : this() { if (serverUri == null) diff --git a/src/Core/Binding/IServerConnectionsRepository.cs b/src/Core/Binding/IServerConnectionsRepository.cs index 21ab89bed8..7c695e6333 100644 --- a/src/Core/Binding/IServerConnectionsRepository.cs +++ b/src/Core/Binding/IServerConnectionsRepository.cs @@ -25,12 +25,19 @@ namespace SonarLint.VisualStudio.Core.Binding; public interface IServerConnectionsRepository { bool TryGet(string connectionId, out ServerConnection serverConnection); + bool TryGetAll(out IReadOnlyList serverConnections); + bool TryAdd(ServerConnection connectionToAdd); + bool TryDelete(string connectionId); + bool TryUpdateSettingsById(string connectionId, ServerConnectionSettings connectionSettings); + bool TryUpdateCredentialsById(string connectionId, IConnectionCredentials credentials); + bool ConnectionsFileExists(); + event EventHandler ConnectionChanged; event EventHandler CredentialsChanged; } @@ -44,4 +51,3 @@ public ServerConnectionUpdatedEventArgs(ServerConnection serverConnection) public ServerConnection ServerConnection { get; } } - diff --git a/src/Core/Binding/ServerConnection.cs b/src/Core/Binding/ServerConnection.cs index d1e51272d5..1c4df106ad 100644 --- a/src/Core/Binding/ServerConnection.cs +++ b/src/Core/Binding/ServerConnection.cs @@ -26,11 +26,11 @@ namespace SonarLint.VisualStudio.Core.Binding; public abstract class ServerConnection { internal static readonly ServerConnectionSettings DefaultSettings = new(true); - + public string Id { get; } public ServerConnectionSettings Settings { get; set; } public IConnectionCredentials Credentials { get; set; } - + public abstract Uri ServerUri { get; } public abstract Uri CredentialsUri { get; } @@ -52,7 +52,7 @@ private ServerConnection(string id, ServerConnectionSettings settings = null, IC public sealed class SonarCloud : ServerConnection { private static readonly string SonarCloudUrl = CoreStrings.SonarCloudUrl; - + public SonarCloud(string organizationKey, ServerConnectionSettings settings = null, IConnectionCredentials credentials = null) : base(OrganizationKeyToId(organizationKey), settings, credentials) { @@ -61,8 +61,8 @@ public SonarCloud(string organizationKey, ServerConnectionSettings settings = nu } public string OrganizationKey { get; } - - public override Uri ServerUri => new (SonarCloudUrl); + + public override Uri ServerUri => new(SonarCloudUrl); public override Uri CredentialsUri { get; } private static string OrganizationKeyToId(string organizationKey) @@ -75,7 +75,7 @@ private static string OrganizationKeyToId(string organizationKey) return $"{SonarCloudUrl}/organizations/{organizationKey}"; } } - + public sealed class SonarQube(Uri serverUri, ServerConnectionSettings settings = null, IConnectionCredentials credentials = null) : ServerConnection(serverUri?.ToString(), settings, credentials) {