Skip to content

Commit

Permalink
SLVS-1732 Fix SSF-694 (#5925)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriela-trutan-sonarsource committed Jan 8, 2025
1 parent 1ee4dde commit 4939006
Show file tree
Hide file tree
Showing 16 changed files with 501 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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;
using SonarQube.Client.Models;

namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence;

[TestClass]
public class CredentialsExtensionMethodsTests
{
[TestMethod]
public void ToCredential_NullCredentials_Throws() => Assert.ThrowsException<NotSupportedException>(() => ((IConnectionCredentials)null).ToCredential());

[TestMethod]
public void ToCredential_BasicAuthCredentials_ReturnsExpected()
{
var basicAuthCredentials = new UsernameAndPasswordCredentials("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 ToConnectionCredentials_UsernameIsEmpty_ReturnsBasicAuthCredentialsWithPasswordAsToken()
{
var credential = new Credential(string.Empty, "token");

var result = credential.ToConnectionCredentials();

var basicAuth = result as UsernameAndPasswordCredentials;
basicAuth.Should().NotBeNull();
basicAuth.UserName.Should().Be(credential.Username);
basicAuth.Password.ToUnsecureString().Should().Be(credential.Password);
}

/// <summary>
/// For backward compatibility
/// </summary>

[TestMethod]
public void ToConnectionCredentials_PasswordIsEmpty_ReturnsTokenAuthCredentialsWithUsernameAsToken()
{
var credential = new Credential("token", string.Empty);

var result = credential.ToConnectionCredentials();

var tokenAuth = result as TokenAuthCredentials;
tokenAuth.Should().NotBeNull();
tokenAuth.Token.ToUnsecureString().Should().Be(credential.Username);
}

[TestMethod]
public void ToConnectionCredentials_PasswordAndUsernameFilled_ReturnsBasicAuthCredentials()
{
var credential = new Credential("username", "pwd");

var result = credential.ToConnectionCredentials();

var basicAuth = result as UsernameAndPasswordCredentials;
basicAuth.Should().NotBeNull();
basicAuth.UserName.Should().Be(credential.Username);
basicAuth.Password.ToUnsecureString().Should().Be(credential.Password);
}

[TestMethod]
public void ToConnectionCredentials_Null_ReturnsNull()
{
var result = ((Credential)null).ToConnectionCredentials();

result.Should().BeNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,56 @@ 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);
}

[TestMethod]
public void Load_CredentialsExist_CredentialsWithSecuredString()
{
var credentials = new Credential("user", "password");
store
.ReadCredentials(Arg.Is<TargetUri>(t => t.ActualUri == mockUri))
.Returns(credentials);
MockReadCredentials(mockUri, credentials);

var actual = testSubject.Load(mockUri);

actual.Should().BeEquivalentTo(new UsernameAndPasswordCredentials("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 UsernameAndPasswordCredentials(string.Empty, "token".ToSecureString()));
}

/// <summary>
/// For backward compatibility
/// </summary>
[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()
{
Expand All @@ -99,8 +125,14 @@ public void Save_CredentialsAreNull_CredentialsNotSaved()
[TestMethod]
public void Save_CredentialsAreNotBasicAuth_CredentialsNotSaved()
{
var mockCredentials = new Mock<IConnectionCredentials>();
testSubject.Save(mockCredentials.Object, mockUri);
try
{
testSubject.Save(new Mock<IConnectionCredentials>().Object, mockUri);
}
catch (Exception)
{
// ignored
}

store.DidNotReceive().WriteCredentials(Arg.Any<TargetUri>(), Arg.Any<Credential>());
}
Expand All @@ -117,6 +149,19 @@ public void Save_CredentialsAreBasicAuth_CredentialsSavedWithUnsecuredString()
Arg.Is<Credential>(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<TargetUri>(t => t.ActualUri == mockUri),
Arg.Is<Credential>(c => c.Username == "token" && c.Password == string.Empty));
}

[TestMethod]
public void DeleteCredentials_UriNull_DoesNotCallStoreDeleteCredentials()
{
Expand All @@ -132,5 +177,10 @@ public void DeleteCredentials_UriProvided_CallsStoreDeleteCredentials()

store.Received(1).DeleteCredentials(Arg.Any<TargetUri>());
}

private void MockReadCredentials(Uri uri, Credential credentials) =>
store
.ReadCredentials(Arg.Is<TargetUri>(t => t.ActualUri == uri))
.Returns(credentials);
}
}
Original file line number Diff line number Diff line change
@@ -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<ArgumentNullException>();
}

[TestMethod]
public void Dispose_DisposesPassword()
{
var testSubject = new TokenAuthCredentials(Token.ToSecureString());

testSubject.Dispose();

Exceptions.Expect<ObjectDisposedException>(() => 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public void TryAddConnection_TokenCredentialsModel_MapsCredentials()
testSubject.TryAddConnection(sonarQube, new TokenCredentialsModel(token.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sq => IsExpectedCredentials(sq.Credentials, token, string.Empty)));
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sq => IsExpectedTokenCredentials(sq.Credentials, token)));
}

[TestMethod]
Expand Down Expand Up @@ -258,7 +258,7 @@ public void TryUpdateCredentials_TokenCredentialsModel_MapsCredentials()
testSubject.TryUpdateCredentials(sonarQube, new TokenCredentialsModel(token.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryUpdateCredentialsById(Arg.Any<string>(), Arg.Is<IConnectionCredentials>(x => IsExpectedCredentials(x, token, string.Empty)));
.TryUpdateCredentialsById(Arg.Any<string>(), Arg.Is<IConnectionCredentials>(x => IsExpectedTokenCredentials(x, token)));
}

[TestMethod]
Expand Down Expand Up @@ -371,6 +371,11 @@ private static bool IsExpectedCredentials(IConnectionCredentials credentials, st
return credentials is UsernameAndPasswordCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword;
}

private static bool IsExpectedTokenCredentials(IConnectionCredentials 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 =>
Expand Down
12 changes: 6 additions & 6 deletions src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 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);
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;
Expand Down Expand Up @@ -280,7 +280,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarQubeWithToken_CallsGetAll
await testSubject.GetAllProjectsAsync(sonarQubeConnection);

await connectionConfigurationSlCoreService.Received(1)
.GetAllProjectsAsync(Arg.Is<GetAllProjectsParams>(x => IsExpectedSonarQubeConnectionParams(x.transientConnection, ValidToken.UserName)));
.GetAllProjectsAsync(Arg.Is<GetAllProjectsParams>(x => IsExpectedSonarQubeConnectionParams(x.transientConnection, ValidTokenAuth.Token.ToUnsecureString())));
}

[TestMethod]
Expand All @@ -302,7 +302,7 @@ public async Task GetAllProjectsAsync_ConnectionToSonarCloudWithToken_CallsGetAl
await testSubject.GetAllProjectsAsync(sonarCloudConnection);

await connectionConfigurationSlCoreService.Received(1)
.GetAllProjectsAsync(Arg.Is<GetAllProjectsParams>(x => IsExpectedSonarCloudConnectionParams(x.transientConnection, ValidToken.UserName)));
.GetAllProjectsAsync(Arg.Is<GetAllProjectsParams>(x => IsExpectedSonarCloudConnectionParams(x.transientConnection, ValidTokenAuth.Token.ToUnsecureString())));
}

[TestMethod]
Expand Down
Loading

0 comments on commit 4939006

Please sign in to comment.