Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLVS-1738 Refactor ConnectionInformation #5919

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ await sonarQubeService
.Received()
.ConnectAsync(
Arg.Is<ConnectionInformation>(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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ArgumentNullException>();
}

[TestMethod]
public void Ctor_WhenPasswordIsNull_ThrowsArgumentNullException()
{
Action act = () => new BasicAuthCredentials(Username, null);

act.Should().Throw<ArgumentNullException>();
}

[TestMethod]
public void Dispose_DisposesPassword()
{
var testSubject = new BasicAuthCredentials(Username, Password.ToSecureString());

testSubject.Dispose();

Exceptions.Expect<ObjectDisposedException>(() => 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<INoCredentials>();
conn.Organization.Key.Should().Be("org_key");
conn.Organization.Name.Should().Be("org_name");
}
Expand All @@ -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");
}
Expand All @@ -83,11 +85,10 @@ public void BoundSonarQubeProject_CreateConnectionInformation_NoOrganizationNoAu

// Assert
conn.ServerUri.Should().Be(input.ServerUri);
conn.UserName.Should().BeNull();
conn.Password.Should().BeNull();
conn.Credentials.Should().BeAssignableTo<INoCredentials>();
conn.Organization.Should().BeNull();
}

[TestMethod]
public void BoundServerProject_CreateConnectionInformation_ArgCheck()
{
Expand All @@ -105,11 +106,9 @@ 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<INoCredentials>();
conn.Organization.Key.Should().Be("org_key");
}


[TestMethod]
public void BoundServerProject_CreateConnectionInformation_BasicAuthCredentials()
Expand All @@ -123,8 +122,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");
}

Expand All @@ -139,8 +140,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<INoCredentials>();
conn.Organization.Should().BeNull();
}
}
12 changes: 9 additions & 3 deletions src/ConnectedMode/Binding/IUnintrusiveBindingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@
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
{
public interface IBindingController
{
Task BindAsync(BoundServerProject project, CancellationToken cancellationToken);

bool Unbind(string localBindingKey);
}

internal interface IUnintrusiveBindingController
{
Task BindAsync(BoundServerProject project, IProgress<FixedStepsProgress> progress, CancellationToken token);
Expand All @@ -47,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;
Expand All @@ -57,7 +63,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);
Expand Down
24 changes: 8 additions & 16 deletions src/ConnectedMode/Persistence/BasicAuthCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
38 changes: 0 additions & 38 deletions src/ConnectedMode/Persistence/ConnectionInfoConverter.cs

This file was deleted.

10 changes: 3 additions & 7 deletions src/Core/Binding/BoundSonarQubeProjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,20 @@ 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;
}

public static ConnectionInformation CreateConnectionInformation(this BoundServerProject binding)
{
if (binding == null)
{
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;
Expand Down
4 changes: 1 addition & 3 deletions src/Core/Binding/ICredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
24 changes: 12 additions & 12 deletions src/Integration.UnitTests/Service/ConnectionInformationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -55,11 +57,11 @@ public void ConnectionInformation_WithLoginInformation()
testSubject.Dispose();

// Assert testSubject
Exceptions.Expect<ObjectDisposedException>(() => testSubject.Password.ToUnsecureString());
Exceptions.Expect<ObjectDisposedException>(() => ((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");
}

Expand All @@ -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<INoCredentials>();
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<INoCredentials>();
testSubject2.ServerUri.Should().Be(serverUri, "ServerUri doesn't match");
}

Expand Down Expand Up @@ -110,7 +110,7 @@ public void ConnectionInformation_Ctor_FixesSonarCloudUri()
public void ConnectionInformation_Ctor_ArgChecks()
{
Exceptions.Expect<ArgumentNullException>(() => new ConnectionInformation(null));
Exceptions.Expect<ArgumentNullException>(() => new ConnectionInformation(null, "user", "pwd".ToSecureString()));
Exceptions.Expect<ArgumentNullException>(() => new ConnectionInformation(null, new BasicAuthCredentials("user", "pwd".ToSecureString())));
}
}
}
Loading
Loading