Skip to content

Commit

Permalink
Add SLCore credentials provider implementation (#5206)
Browse files Browse the repository at this point in the history
Fixes #5204
  • Loading branch information
georgii-borovinskikh-sonarsource authored Feb 9, 2024
1 parent ee90d47 commit 6a8a0b5
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
################################
# Assembly references report
# Report date/time: 2024-02-07T10:15:27.0203827Z
# Report date/time: 2024-02-07T13:54:19.6745665Z
################################
#
# Generated by Devtility CheckAsmRefs v0.11.0.223
Expand Down Expand Up @@ -458,8 +458,13 @@ Assembly: 'SonarLint.VisualStudio.SLCore.Listeners, Version=7.7.0.0, Culture=neu
Relative path: 'SonarLint.VisualStudio.SLCore.Listeners.dll'

Referenced assemblies:
- 'Microsoft.Alm.Authentication, Version=4.0.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
- 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
# Number of references: 1
- 'SonarLint.VisualStudio.ConnectedMode, Version=7.7.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
- 'SonarLint.VisualStudio.SLCore, Version=7.7.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
- 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
# Number of references: 6

---
Assembly: 'SonarLint.VisualStudio.TypeScript, Version=7.7.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
################################
# Assembly references report
# Report date/time: 2024-02-07T10:15:27.0203827Z
# Report date/time: 2024-02-07T13:54:19.6745665Z
################################
#
# Generated by Devtility CheckAsmRefs v0.11.0.223
Expand Down Expand Up @@ -458,8 +458,13 @@ Assembly: 'SonarLint.VisualStudio.SLCore.Listeners, Version=7.7.0.0, Culture=neu
Relative path: 'SonarLint.VisualStudio.SLCore.Listeners.dll'

Referenced assemblies:
- 'Microsoft.Alm.Authentication, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null'
- 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
# Number of references: 1
- 'SonarLint.VisualStudio.ConnectedMode, Version=7.7.0.0, Culture=neutral, PublicKeyToken=null'
- 'SonarLint.VisualStudio.SLCore, Version=7.7.0.0, Culture=neutral, PublicKeyToken=null'
- 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
# Number of references: 6

---
Assembly: 'SonarLint.VisualStudio.TypeScript, Version=7.7.0.0, Culture=neutral, PublicKeyToken=null'
Expand Down
137 changes: 137 additions & 0 deletions src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Alm.Authentication;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SonarLint.VisualStudio.ConnectedMode.Binding;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Common.Models;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Listener.Credentials;
using SonarLint.VisualStudio.SLCore.Listeners.Implementation;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests;

[TestClass]
public class CredentialsListenerTests
{
private const string ConnectionId = "connectionId123";
private static readonly Uri Uri = new("http://myfavouriteuri.nonexistingdomain");

[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<CredentialsListener, ISLCoreListener>(
MefTestHelpers.CreateExport<ICredentialStoreService>());
}

[TestMethod]
public void MefCtor_CheckIsSingleton()
{
MefTestHelpers.CheckIsSingletonMefComponent<CredentialsListener>();
}

[TestMethod]
public async Task GetCredentialsAsync_NullConnectionId_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out _, out var connectionIdHelperMock);
connectionIdHelperMock.Setup(x => x.GetUriFromConnectionId(null)).Returns((Uri)null);

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(null));

response.Should().BeSameAs(GetCredentialsResponse.NoCredentials);
}

[TestMethod]
public async Task GetCredentialsAsync_NullParams_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out _, out var connectionIdHelperMock);
connectionIdHelperMock.Setup(x => x.GetUriFromConnectionId(null)).Returns((Uri)null);

var response = await testSubject.GetCredentialsAsync(null);

response.Should().BeSameAs(GetCredentialsResponse.NoCredentials);
}

[TestMethod]
public async Task GetCredentialsAsync_CredentialsNotFound_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
credentialStoreMock.Setup(x => x.ReadCredentials(It.Is((TargetUri targetUri) => UriEquals(targetUri, Uri)))).Returns((Credential)null);

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));

response.Should().BeSameAs(GetCredentialsResponse.NoCredentials);
}


[TestMethod]
public async Task GetCredentialsAsync_SonarQubeUsernameAndPasswordFound_ReturnsUsernameAndPassword()
{
const string username = "user1";
const string password = "password123";

var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
credentialStoreMock.Setup(x => x.ReadCredentials(It.Is((TargetUri targetUri) => UriEquals(targetUri, Uri)))).Returns(new Credential(username, password));

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));

response.Should().BeEquivalentTo(new GetCredentialsResponse(new UsernamePasswordDto(username, password)));
}

[TestMethod]
public async Task GetCredentialsAsync_SonarQubeTokenFound_ReturnsToken()
{
const string token = "token123";

var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
credentialStoreMock.Setup(x => x.ReadCredentials(It.Is((TargetUri targetUri) => UriEquals(targetUri, Uri)))).Returns(new Credential(token));

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));

response.Should().BeEquivalentTo(new GetCredentialsResponse(new TokenDto(token)));
}

private CredentialsListener CreateTestSubject(out Mock<ICredentialStoreService> credentialStoreMock, out Mock<IConnectionIdHelper> connectionIdHelperMock)
{
credentialStoreMock = new Mock<ICredentialStoreService>();
connectionIdHelperMock = new Mock<IConnectionIdHelper>();

return new CredentialsListener(credentialStoreMock.Object, connectionIdHelperMock.Object);
}

private static void SetUpConnectionIdHelper(Mock<IConnectionIdHelper> connectionIdHelperMock)
{
connectionIdHelperMock.Setup(x => x.GetUriFromConnectionId(ConnectionId)).Returns(Uri);
}

private static bool UriEquals(TargetUri uri, Uri serverUri)
{
return serverUri.Equals((Uri)uri);
}
}
74 changes: 74 additions & 0 deletions src/SLCore.Listeners/Implementation/CredentialsListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.ComponentModel.Composition;
using System.Threading.Tasks;
using SonarLint.VisualStudio.ConnectedMode.Binding;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Common.Models;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Listener.Credentials;

namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation
{
/// <summary>
/// Credentials provider for SLCore
/// </summary>
[Export(typeof(ISLCoreListener))]
[PartCreationPolicy(CreationPolicy.Shared)]
internal class CredentialsListener : ICredentialsListener
{
private readonly ICredentialStoreService credentialStore;
private readonly IConnectionIdHelper connectionIdHelper;

[ImportingConstructor]
public CredentialsListener(ICredentialStoreService credentialStore)
: this(credentialStore, new ConnectionIdHelper())
{
}

public CredentialsListener(ICredentialStoreService credentialStore, IConnectionIdHelper connectionIdHelper)
{
this.credentialStore = credentialStore;
this.connectionIdHelper = connectionIdHelper;
}

public Task<GetCredentialsResponse> GetCredentialsAsync(GetCredentialsParams parameters)
{
var serverUri = connectionIdHelper.GetUriFromConnectionId(parameters?.connectionId);

if (serverUri == null)
{
return Task.FromResult(GetCredentialsResponse.NoCredentials);
}

var credentials = credentialStore.ReadCredentials(serverUri);

if (credentials == null)
{
return Task.FromResult(GetCredentialsResponse.NoCredentials);
}

return Task.FromResult(string.IsNullOrEmpty(credentials.Password)
? new GetCredentialsResponse(new TokenDto(credentials.Username))
: new GetCredentialsResponse(new UsernamePasswordDto(credentials.Username, credentials.Password)));
}
}
}
32 changes: 32 additions & 0 deletions src/SLCore.Listeners/InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.Runtime.CompilerServices;

#if SignAssembly
[assembly: InternalsVisibleTo("SonarLint.VisualStudio.SLCore.UnitTests,PublicKey=002400000480000094000000060200000024000052534131000400000100010081b4345a022cc0f4b42bdc795a5a7a1623c1e58dc2246645d751ad41ba98f2749dc5c4e0da3a9e09febcb2cd5b088a0f041f8ac24b20e736d8ae523061733782f9c4cd75b44f17a63714aced0b29a59cd1ce58d8e10ccdb6012c7098c39871043b7241ac4ab9f6b34f183db716082cd57c1ff648135bece256357ba735e67dc6")]
[assembly: InternalsVisibleTo("SonarLint.VisualStudio.SLCore.Listeners.UnitTests,PublicKey=002400000480000094000000060200000024000052534131000400000100010081b4345a022cc0f4b42bdc795a5a7a1623c1e58dc2246645d751ad41ba98f2749dc5c4e0da3a9e09febcb2cd5b088a0f041f8ac24b20e736d8ae523061733782f9c4cd75b44f17a63714aced0b29a59cd1ce58d8e10ccdb6012c7098c39871043b7241ac4ab9f6b34f183db716082cd57c1ff648135bece256357ba735e67dc6")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
#else

[assembly: InternalsVisibleTo("SonarLint.VisualStudio.SLCore.UnitTests")]
[assembly: InternalsVisibleTo("SonarLint.VisualStudio.SLCore.Listeners.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
#endif
49 changes: 49 additions & 0 deletions src/SLCore.UnitTests/Common/Helpers/ConnectionIdHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.SLCore.Common.Helpers;

namespace SonarLint.VisualStudio.SLCore.UnitTests.Common.Helpers;

[TestClass]
public class ConnectionIdHelperTests
{
[DataTestMethod]
[DataRow(null, null)]
[DataRow("", null)]
[DataRow("http://someuri.abcdef", null)]
[DataRow("sq", null)]
[DataRow("sq|", null)]
[DataRow("sq|http://someuri.abcdef", "http://someuri.abcdef")]
[DataRow("sq|https://someuri.abcdef", "https://someuri.abcdef")]
[DataRow("sq|https://someuri.abcdef/123", "https://someuri.abcdef/123")]
[DataRow("sc", null)]
[DataRow("sc|", null)]
[DataRow("sc|myorganization", "https://sonarcloud.io")]
[DataRow("sc|https://someuri.abcdef/123", "https://sonarcloud.io")] // should not happen, but we ignore any value after "sc|"
public void ReturnsAsExpected(string connectionId, string expectedUri)
{
var uri = new ConnectionIdHelper().GetUriFromConnectionId(connectionId);

var stringUri = uri?.ToString().Trim('/'); //trim is because uri.ToString sometimes adds / at the end. This won't be a problem in real code since we use Uri-s and not strings, but for simplicity this tests asserts string equality

stringUri.Should().BeEquivalentTo(expectedUri);
}
}
Loading

0 comments on commit 6a8a0b5

Please sign in to comment.