From b62323df91fe73aeafae4715d78bfe8a30ca128b Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 11 Jul 2025 14:47:17 +0200 Subject: [PATCH 1/3] i --- src/CFamily.UnitTests/packages.lock.json | 8 +- .../ServerConnectionsRepositoryTests.cs | 6 +- .../SolutionBindingRepositoryTests.cs | 8 +- .../packages.lock.json | 8 +- src/ConnectedMode/ConnectedMode.csproj | 4 + .../CredentialStore2/CredentialStore2.cs | 253 ++++++++++++++++++ .../ISolutionBindingCredentialsLoader.cs | 2 +- .../ServerConnectionsRepository.cs | 7 +- .../Persistence/SolutionBindingFileLoader.cs | 4 + .../Persistence/SolutionBindingRepository.cs | 17 +- src/ConnectedMode/packages.lock.json | 6 + src/Core.UnitTests/packages.lock.json | 8 +- src/Education.UnitTests/packages.lock.json | 8 +- .../packages.lock.json | 8 +- src/Integration.UnitTests/packages.lock.json | 8 +- .../packages.lock.json | 8 +- ...egration.Vsix_Baseline_WithStrongNames.txt | 10 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 10 +- .../SonarLintDaemonPackage.cs | 4 +- src/Integration.Vsix/packages.lock.json | 8 +- src/Integration/packages.lock.json | 8 +- .../packages.lock.json | 8 +- src/IssueViz.Security/packages.lock.json | 8 +- src/IssueViz.UnitTests/packages.lock.json | 8 +- .../packages.lock.json | 8 +- .../Roslyn.Suppressions/packages.lock.json | 8 +- .../packages.lock.json | 8 +- .../CredentialsListenerTests.cs | 232 ++++++++-------- .../packages.lock.json | 8 +- .../Implementation/CredentialsListener.cs | 28 +- src/SLCore.Listeners/packages.lock.json | 8 +- src/SLCore.UnitTests/packages.lock.json | 8 +- src/TestInfrastructure/packages.lock.json | 8 +- 33 files changed, 567 insertions(+), 176 deletions(-) create mode 100644 src/ConnectedMode/CredentialStore2/CredentialStore2.cs diff --git a/src/CFamily.UnitTests/packages.lock.json b/src/CFamily.UnitTests/packages.lock.json index 975bb3b8e6..99ffa2587d 100644 --- a/src/CFamily.UnitTests/packages.lock.json +++ b/src/CFamily.UnitTests/packages.lock.json @@ -1115,6 +1115,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1286,7 +1291,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs index 1ca940b83f..779bc209bf 100644 --- a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionsRepositoryTests.cs @@ -61,7 +61,8 @@ public void MefCtor_CheckExports() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); [TestMethod] @@ -69,7 +70,8 @@ public void MefCtor_IServerConnectionWithInvalidTokenRepository_CheckExports() = MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); [TestMethod] diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs index 47973d48fc..f3a904e27d 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs @@ -56,7 +56,7 @@ public void TestInitialize() solutionBindingFileLoader = Substitute.For(); logger = new TestLogger(); - testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider, bindingJsonModelConverter, serverConnectionsRepository, solutionBindingFileLoader, credentialsLoader, logger); + testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider, bindingJsonModelConverter, serverConnectionsRepository, credentialsLoader, solutionBindingFileLoader, logger); mockCredentials = new UsernameAndPasswordCredentials("user", "pwd".ToSecureString()); @@ -72,13 +72,15 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } diff --git a/src/ConnectedMode.UnitTests/packages.lock.json b/src/ConnectedMode.UnitTests/packages.lock.json index eb0a0b7c95..31fb288868 100644 --- a/src/ConnectedMode.UnitTests/packages.lock.json +++ b/src/ConnectedMode.UnitTests/packages.lock.json @@ -1334,6 +1334,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1483,7 +1488,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/ConnectedMode/ConnectedMode.csproj b/src/ConnectedMode/ConnectedMode.csproj index 9143f1fe3e..8b6a7b311e 100644 --- a/src/ConnectedMode/ConnectedMode.csproj +++ b/src/ConnectedMode/ConnectedMode.csproj @@ -171,6 +171,10 @@ + + + + diff --git a/src/ConnectedMode/CredentialStore2/CredentialStore2.cs b/src/ConnectedMode/CredentialStore2/CredentialStore2.cs new file mode 100644 index 0000000000..ac33a0fff6 --- /dev/null +++ b/src/ConnectedMode/CredentialStore2/CredentialStore2.cs @@ -0,0 +1,253 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2025 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.IO; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json; +using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Core.Synchronization; +using SonarLint.VisualStudio.Core.SystemAbstractions; +using SonarQube.Client.Helpers; + +namespace SonarLint.VisualStudio.ConnectedMode.CredentialStore2; + +internal class CredentialDto +{ + public Uri Uri { get; init; } + public string EncryptedToken { get; init; } +} + +[Export(typeof(ISolutionBindingCredentialsLoader))] +[PartCreationPolicy(CreationPolicy.Shared)] +public class CredentialStore2 : ISolutionBindingCredentialsLoader, IDisposable +{ + private readonly IFileSystemService fileSystem; + private readonly IAsyncLock asyncLock; + private readonly string storagePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SLVS_Credentials", "credentials.json"); + private readonly SecureString masterPassword; + private bool disposed = false; + + [ImportingConstructor] + public CredentialStore2(IFileSystemService fileSystem, IAsyncLockFactory asyncLockFactory) + { + this.fileSystem = fileSystem; + asyncLock = asyncLockFactory.Create(); + + masterPassword = new SecureString(); + foreach (char c in "testpassword") + { + masterPassword.AppendChar(c); + } + masterPassword.MakeReadOnly(); + } + + public void DeleteCredentials(Uri targetUri) + { + ThrowIfDisposed(); + + using (asyncLock.Acquire()) + { + if (targetUri == null || !fileSystem.File.Exists(storagePath)) + { + return; + } + + var allText = fileSystem.File.ReadAllText(storagePath); + var dictionary = JsonConvert.DeserializeObject>(allText); + + if (dictionary != null && dictionary.Remove(targetUri)) + { + var serializedDictionary = JsonConvert.SerializeObject(dictionary); + fileSystem.File.WriteAllText(storagePath, serializedDictionary); + } + } + } + + public IConnectionCredentials Load(Uri boundServerUri) + { + ThrowIfDisposed(); + + using (asyncLock.Acquire()) + { + string encryptedToken = ReadToken(boundServerUri); + + if (encryptedToken == null) + { + return null; + } + + var secureToken = GetSecureString(encryptedToken); + return new TokenAuthCredentials(secureToken); + } + } + + public void Save(IConnectionCredentials credentials, Uri boundServerUri) + { + ThrowIfDisposed(); + + if (credentials is not ITokenCredentials tokenCredentials) + { + throw new ArgumentException("Only token credentials are supported", nameof(credentials)); + } + + using (asyncLock.Acquire()) + { + var tokenProtectedBytes = UseMasterPasswordSafe(masterPasswordBytes => + { + byte[] tokenUnprotected = null; + byte[] tokenProtected = null; + try + { + tokenUnprotected = Encoding.UTF8.GetBytes(tokenCredentials.Token.ToUnsecureString()); + tokenProtected = ProtectedData.Protect( + tokenUnprotected, + masterPasswordBytes, + DataProtectionScope.LocalMachine); + } + finally + { + Clear(tokenUnprotected); + } + + return tokenProtected; + }); + + WriteToken(boundServerUri, Convert.ToBase64String(tokenProtectedBytes)); + } + } + + private SecureString GetSecureString(string encryptedToken) + { + SecureString secureToken = new SecureString(); + byte[] tokenUnprotectedBytes = null; + string unprotectedString; + try + { + tokenUnprotectedBytes = UseMasterPasswordSafe(masterPasswordBytes => + ProtectedData.Unprotect( + Convert.FromBase64String(encryptedToken), + masterPasswordBytes, + DataProtectionScope.LocalMachine)); + unprotectedString = Encoding.UTF8.GetString(tokenUnprotectedBytes); + } + finally + { + Clear(tokenUnprotectedBytes); + } + + foreach (var character in unprotectedString) + { + secureToken.AppendChar(character); + } + secureToken.MakeReadOnly(); + + return secureToken; + } + + private byte[] UseMasterPasswordSafe(Func operation) + { + byte[] masterPasswordUnprotectedBytes = null; + byte[] result = null; + try + { + masterPasswordUnprotectedBytes = Encoding.UTF8.GetBytes(masterPassword.ToUnsecureString()); + result = operation(masterPasswordUnprotectedBytes); + } + finally + { + Clear(masterPasswordUnprotectedBytes); + } + return result; + } + + private string ReadToken(Uri targetUri) + { + if (fileSystem.File.Exists(storagePath)) + { + var allText = fileSystem.File.ReadAllText(storagePath); + var dictionary = JsonConvert.DeserializeObject>(allText); + if (dictionary != null && dictionary.TryGetValue(targetUri, out var dto)) + { + return dto.EncryptedToken; + } + } + + return null; + } + + private void WriteToken(Uri targetUri, string token) + { + Dictionary dictionary; + + if (fileSystem.File.Exists(storagePath)) + { + var allText = fileSystem.File.ReadAllText(storagePath); + dictionary = JsonConvert.DeserializeObject>(allText) ?? new Dictionary(); + } + else + { + dictionary = new Dictionary(); + + var directory = Path.GetDirectoryName(storagePath); + if (!fileSystem.Directory.Exists(directory)) + { + fileSystem.Directory.CreateDirectory(directory); + } + } + + dictionary[targetUri] = new CredentialDto { Uri = targetUri, EncryptedToken = token }; + var serializedDictionary = JsonConvert.SerializeObject(dictionary); + + fileSystem.File.WriteAllText(storagePath, serializedDictionary); + } + + private void Clear(byte[] array) + { + if (array is null) + { + return; + } + Array.Clear(array, 0, array.Length); + } + + private void ThrowIfDisposed() + { + if (disposed) + { + throw new ObjectDisposedException(nameof(CredentialStore2)); + } + } + + public void Dispose() + { + if (disposed) + { + return; + } + + asyncLock.Dispose(); + masterPassword?.Dispose(); + disposed = true; + } +} diff --git a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs index 1b98cde500..6cf1b7f07f 100644 --- a/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs +++ b/src/ConnectedMode/Persistence/ISolutionBindingCredentialsLoader.cs @@ -22,7 +22,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence { - interface ISolutionBindingCredentialsLoader + public interface ISolutionBindingCredentialsLoader { void DeleteCredentials(Uri boundServerUri); diff --git a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs index 2a9e76ba6a..5676b7109b 100644 --- a/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs +++ b/src/ConnectedMode/Persistence/ServerConnectionsRepository.cs @@ -52,11 +52,12 @@ internal class ServerConnectionsRepository : IServerConnectionsRepository, IServ public ServerConnectionsRepository( IJsonFileHandler jsonFileHandle, IServerConnectionModelMapper serverConnectionModelMapper, - ICredentialStoreService credentialStoreService, + ISolutionBindingCredentialsLoader credentialsLoader, + IEnvironmentVariableProvider environmentVariables, ILogger logger) : this(jsonFileHandle, serverConnectionModelMapper, - new SolutionBindingCredentialsLoader(credentialStoreService), - EnvironmentVariableProvider.Instance, + credentialsLoader, + environmentVariables, new FileSystem(), logger) { diff --git a/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs index 789188f0b1..2dee8e91c7 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.ComponentModel.Composition; using System.IO; using System.IO.Abstractions; using Newtonsoft.Json; @@ -26,11 +27,14 @@ namespace SonarLint.VisualStudio.ConnectedMode.Persistence; +[Export(typeof(ISolutionBindingFileLoader))] +[PartCreationPolicy(CreationPolicy.Shared)] internal class SolutionBindingFileLoader : ISolutionBindingFileLoader { private readonly IFileSystem fileSystem; private readonly ILogger logger; + [ImportingConstructor] public SolutionBindingFileLoader(ILogger logger) : this(logger, new FileSystem()) { diff --git a/src/ConnectedMode/Persistence/SolutionBindingRepository.cs b/src/ConnectedMode/Persistence/SolutionBindingRepository.cs index dd9864229e..58911f403c 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingRepository.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingRepository.cs @@ -42,23 +42,8 @@ public SolutionBindingRepository( IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, IBindingJsonModelConverter bindingJsonModelConverter, IServerConnectionsRepository serverConnectionsRepository, - ICredentialStoreService credentialStoreService, - ILogger logger) - : this(unintrusiveBindingPathProvider, - bindingJsonModelConverter, - serverConnectionsRepository, - new SolutionBindingFileLoader(logger), - new SolutionBindingCredentialsLoader(credentialStoreService), - logger) - { - } - - internal /* for testing */ SolutionBindingRepository( - IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, - IBindingJsonModelConverter bindingJsonModelConverter, - IServerConnectionsRepository serverConnectionsRepository, - ISolutionBindingFileLoader solutionBindingFileLoader, ISolutionBindingCredentialsLoader credentialsLoader, + ISolutionBindingFileLoader solutionBindingFileLoader, ILogger logger) { this.unintrusiveBindingPathProvider = unintrusiveBindingPathProvider; diff --git a/src/ConnectedMode/packages.lock.json b/src/ConnectedMode/packages.lock.json index 30aae1ca8d..e2da13970a 100644 --- a/src/ConnectedMode/packages.lock.json +++ b/src/ConnectedMode/packages.lock.json @@ -118,6 +118,12 @@ "resolved": "0.0.8", "contentHash": "7GAMIhQSKabPrXVtEpDQoLRWVZUn1IGzeHvfDSvs5lPhU4KPFMQVpI/lDFikhMXr8FmdFBOBGaHEhQ9azMK3TA==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Direct", + "requested": "[9.0.7, )", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "BouncyCastle.Cryptography": { "type": "Transitive", "resolved": "2.4.0", diff --git a/src/Core.UnitTests/packages.lock.json b/src/Core.UnitTests/packages.lock.json index 33a58cfe9b..f9c6ce1f4a 100644 --- a/src/Core.UnitTests/packages.lock.json +++ b/src/Core.UnitTests/packages.lock.json @@ -1088,6 +1088,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1229,7 +1234,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Education.UnitTests/packages.lock.json b/src/Education.UnitTests/packages.lock.json index 89711facab..c9433d4d3e 100644 --- a/src/Education.UnitTests/packages.lock.json +++ b/src/Education.UnitTests/packages.lock.json @@ -1099,6 +1099,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1240,7 +1245,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Infrastructure.VS.UnitTests/packages.lock.json b/src/Infrastructure.VS.UnitTests/packages.lock.json index f3573e697a..b58d280edd 100644 --- a/src/Infrastructure.VS.UnitTests/packages.lock.json +++ b/src/Infrastructure.VS.UnitTests/packages.lock.json @@ -1299,6 +1299,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1448,7 +1453,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Integration.UnitTests/packages.lock.json b/src/Integration.UnitTests/packages.lock.json index 76b6b1dc02..1f83e95945 100644 --- a/src/Integration.UnitTests/packages.lock.json +++ b/src/Integration.UnitTests/packages.lock.json @@ -1095,6 +1095,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1236,7 +1241,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Integration.Vsix.UnitTests/packages.lock.json b/src/Integration.Vsix.UnitTests/packages.lock.json index a8135ba20b..8c8901a758 100644 --- a/src/Integration.Vsix.UnitTests/packages.lock.json +++ b/src/Integration.Vsix.UnitTests/packages.lock.json @@ -1120,6 +1120,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1291,7 +1296,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index f93b06268a..50d805778a 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -1,7 +1,7 @@ --- ################################ # Assembly references report -# Report date/time: 2025-07-07T15:49:32.9451548Z +# Report date/time: 2025-07-11T12:21:06.0203582Z ################################ # # Generated by Devtility CheckAsmRefs v0.11.0.223 @@ -19,7 +19,7 @@ # ################################ -# Base directory: C:\Code\sonarlint-visualstudio\src\Integration.Vsix\bin\Debug\VS2022\net472 +# Base directory: C:\Users\georgii.borovinskikh\Documents\Repos\sonarlint-visualstudio\src\Integration.Vsix\bin\Debug\VS2022\net472 Include patterns: - 'Sonar*' Exclude patterns: @@ -107,12 +107,13 @@ Referenced assemblies: - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' +- 'System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.Threading.Channels, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' - 'System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' -# Number of references: 28 +# Number of references: 29 --- Assembly: 'SonarLint.VisualStudio.Core, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' @@ -331,11 +332,12 @@ Referenced assemblies: - 'SonarLint.VisualStudio.IssueVisualization, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'SonarLint.VisualStudio.IssueVisualization.Security, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'SonarLint.VisualStudio.SLCore, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' +- 'SonarQube.Client, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 10 +# Number of references: 11 --- Assembly: 'SonarQube.Client, Version=8.23.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt index 7579fc3f3d..2983e56aa5 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -1,7 +1,7 @@ --- ################################ # Assembly references report -# Report date/time: 2025-07-07T15:49:32.9451548Z +# Report date/time: 2025-07-11T12:21:06.0203582Z ################################ # # Generated by Devtility CheckAsmRefs v0.11.0.223 @@ -19,7 +19,7 @@ # ################################ -# Base directory: C:\Code\sonarlint-visualstudio\src\Integration.Vsix\bin\Debug\VS2022\net472 +# Base directory: C:\Users\georgii.borovinskikh\Documents\Repos\sonarlint-visualstudio\src\Integration.Vsix\bin\Debug\VS2022\net472 Include patterns: - 'Sonar*' Exclude patterns: @@ -107,12 +107,13 @@ Referenced assemblies: - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' +- 'System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.Threading.Channels, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' - 'System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' -# Number of references: 28 +# Number of references: 29 --- Assembly: 'SonarLint.VisualStudio.Core, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' @@ -331,11 +332,12 @@ Referenced assemblies: - 'SonarLint.VisualStudio.IssueVisualization, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' - 'SonarLint.VisualStudio.IssueVisualization.Security, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' - 'SonarLint.VisualStudio.SLCore, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' +- 'SonarQube.Client, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 10 +# Number of references: 11 --- Assembly: 'SonarQube.Client, Version=8.23.0.0, Culture=neutral, PublicKeyToken=null' diff --git a/src/Integration.Vsix/SonarLintDaemonPackage.cs b/src/Integration.Vsix/SonarLintDaemonPackage.cs index 50b9f1b371..262f7ad97d 100644 --- a/src/Integration.Vsix/SonarLintDaemonPackage.cs +++ b/src/Integration.Vsix/SonarLintDaemonPackage.cs @@ -24,13 +24,13 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using SonarLint.VisualStudio.ConnectedMode.Migration; +using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Infrastructure.VS.Roslyn; using SonarLint.VisualStudio.Integration.CSharpVB.Install; using SonarLint.VisualStudio.Integration.Vsix.Analysis; -using SonarLint.VisualStudio.Integration.Vsix.CFamily; using SonarLint.VisualStudio.Integration.Vsix.Events; using SonarLint.VisualStudio.Integration.Vsix.Resources; using SonarLint.VisualStudio.SLCore; @@ -101,6 +101,8 @@ private async Task InitAsync() logger.WriteLine(Strings.Daemon_Initializing); // This migration should be performed before initializing other services, independent if a solution or a folder is opened. + + var solutionBindingRepository = this.GetMefService(); await MigrateBindingsToServerConnectionsIfNeededAsync(); await MuteIssueCommand.InitializeAsync(this, logger); diff --git a/src/Integration.Vsix/packages.lock.json b/src/Integration.Vsix/packages.lock.json index c3ad73928d..0416cb0ba3 100644 --- a/src/Integration.Vsix/packages.lock.json +++ b/src/Integration.Vsix/packages.lock.json @@ -1315,6 +1315,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1465,7 +1470,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Integration/packages.lock.json b/src/Integration/packages.lock.json index a541b4f223..0513d08539 100644 --- a/src/Integration/packages.lock.json +++ b/src/Integration/packages.lock.json @@ -1018,6 +1018,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1159,7 +1164,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/IssueViz.Security.UnitTests/packages.lock.json b/src/IssueViz.Security.UnitTests/packages.lock.json index 57c42a196f..8f913fde96 100644 --- a/src/IssueViz.Security.UnitTests/packages.lock.json +++ b/src/IssueViz.Security.UnitTests/packages.lock.json @@ -1110,6 +1110,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1251,7 +1256,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/IssueViz.Security/packages.lock.json b/src/IssueViz.Security/packages.lock.json index 815fb79e24..b5e615c9db 100644 --- a/src/IssueViz.Security/packages.lock.json +++ b/src/IssueViz.Security/packages.lock.json @@ -1018,6 +1018,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1159,7 +1164,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/IssueViz.UnitTests/packages.lock.json b/src/IssueViz.UnitTests/packages.lock.json index 33a58cfe9b..f9c6ce1f4a 100644 --- a/src/IssueViz.UnitTests/packages.lock.json +++ b/src/IssueViz.UnitTests/packages.lock.json @@ -1088,6 +1088,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1229,7 +1234,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions.UnitTests/packages.lock.json b/src/Roslyn.Suppressions/Roslyn.Suppressions.UnitTests/packages.lock.json index 53977d1d7c..ce5814af68 100644 --- a/src/Roslyn.Suppressions/Roslyn.Suppressions.UnitTests/packages.lock.json +++ b/src/Roslyn.Suppressions/Roslyn.Suppressions.UnitTests/packages.lock.json @@ -1125,6 +1125,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1274,7 +1279,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/Roslyn.Suppressions/Roslyn.Suppressions/packages.lock.json b/src/Roslyn.Suppressions/Roslyn.Suppressions/packages.lock.json index 17b4e630d2..1165786917 100644 --- a/src/Roslyn.Suppressions/Roslyn.Suppressions/packages.lock.json +++ b/src/Roslyn.Suppressions/Roslyn.Suppressions/packages.lock.json @@ -1091,6 +1091,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1240,7 +1245,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/SLCore.IntegrationTests/packages.lock.json b/src/SLCore.IntegrationTests/packages.lock.json index 975bb3b8e6..99ffa2587d 100644 --- a/src/SLCore.IntegrationTests/packages.lock.json +++ b/src/SLCore.IntegrationTests/packages.lock.json @@ -1115,6 +1115,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1286,7 +1291,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs b/src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs index d50ce7cda6..515b97be0a 100644 --- a/src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs +++ b/src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs @@ -1,117 +1,119 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2025 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. - */ +// /* +// * SonarLint for Visual Studio +// * Copyright (C) 2016-2025 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.Binding; +// using SonarLint.VisualStudio.SLCore.Common.Models; +// using SonarLint.VisualStudio.SLCore.Core; +// using SonarLint.VisualStudio.SLCore.Listener.Credentials; +// +// namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests; +// +// [TestClass] +// public class CredentialsListenerTests +// { +// private const string ConnectionId = "http://myfavouriteuri.nonexistingdomain"; +// private static readonly Uri Uri = new(ConnectionId); +// +// [TestMethod] +// public void MefCtor_CheckIsExported() +// { +// MefTestHelpers.CheckTypeCanBeImported( +// MefTestHelpers.CreateExport()); +// } +// +// [TestMethod] +// public void MefCtor_CheckIsSingleton() +// { +// MefTestHelpers.CheckIsSingletonMefComponent(); +// } +// +// [TestMethod] +// public async Task GetCredentialsAsync_NullConnectionId_ReturnsNoCredentials() +// { +// var testSubject = CreateTestSubject(out _); +// +// var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(null)); +// +// response.Should().BeSameAs(GetCredentialsResponse.NoCredentials); +// } +// +// [TestMethod] +// public async Task GetCredentialsAsync_NullParams_ReturnsNoCredentials() +// { +// var testSubject = CreateTestSubject(out _); +// +// var response = await testSubject.GetCredentialsAsync(null); +// +// response.Should().BeSameAs(GetCredentialsResponse.NoCredentials); +// } +// +// [TestMethod] +// public async Task GetCredentialsAsync_CredentialsNotFound_ReturnsNoCredentials() +// { +// var testSubject = CreateTestSubject(out var credentialStoreMock); +// credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns((ConnectionCredentials)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); +// credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(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); +// credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(token)); +// +// var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId)); +// +// response.Should().BeEquivalentTo(new GetCredentialsResponse(new TokenDto(token))); +// } +// +// private CredentialsListener CreateTestSubject(out ICredentialProvider credentialStoreMock) +// { +// credentialStoreMock = Substitute.For(); +// +// return new CredentialsListener(credentialStoreMock); +// } +// +// private static bool UriEquals(Uri uri, Uri serverUri) +// { +// return serverUri.ToString() == uri.ToString(); +// } +// } -using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.SLCore.Common.Models; -using SonarLint.VisualStudio.SLCore.Core; -using SonarLint.VisualStudio.SLCore.Listener.Credentials; -namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests; - -[TestClass] -public class CredentialsListenerTests -{ - private const string ConnectionId = "http://myfavouriteuri.nonexistingdomain"; - private static readonly Uri Uri = new(ConnectionId); - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void MefCtor_CheckIsSingleton() - { - MefTestHelpers.CheckIsSingletonMefComponent(); - } - - [TestMethod] - public async Task GetCredentialsAsync_NullConnectionId_ReturnsNoCredentials() - { - var testSubject = CreateTestSubject(out _); - - var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(null)); - - response.Should().BeSameAs(GetCredentialsResponse.NoCredentials); - } - - [TestMethod] - public async Task GetCredentialsAsync_NullParams_ReturnsNoCredentials() - { - var testSubject = CreateTestSubject(out _); - - var response = await testSubject.GetCredentialsAsync(null); - - response.Should().BeSameAs(GetCredentialsResponse.NoCredentials); - } - - [TestMethod] - public async Task GetCredentialsAsync_CredentialsNotFound_ReturnsNoCredentials() - { - var testSubject = CreateTestSubject(out var credentialStoreMock); - credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns((ConnectionCredentials)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); - credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(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); - credentialStoreMock.GetCredentials(Arg.Is(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(token)); - - var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId)); - - response.Should().BeEquivalentTo(new GetCredentialsResponse(new TokenDto(token))); - } - - private CredentialsListener CreateTestSubject(out ICredentialProvider credentialStoreMock) - { - credentialStoreMock = Substitute.For(); - - return new CredentialsListener(credentialStoreMock); - } - - private static bool UriEquals(Uri uri, Uri serverUri) - { - return serverUri.ToString() == uri.ToString(); - } -} diff --git a/src/SLCore.Listeners.UnitTests/packages.lock.json b/src/SLCore.Listeners.UnitTests/packages.lock.json index cb60c76013..bbdf84a57b 100644 --- a/src/SLCore.Listeners.UnitTests/packages.lock.json +++ b/src/SLCore.Listeners.UnitTests/packages.lock.json @@ -1100,6 +1100,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1241,7 +1246,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/SLCore.Listeners/Implementation/CredentialsListener.cs b/src/SLCore.Listeners/Implementation/CredentialsListener.cs index 0f30f5f16e..9ace4e0220 100644 --- a/src/SLCore.Listeners/Implementation/CredentialsListener.cs +++ b/src/SLCore.Listeners/Implementation/CredentialsListener.cs @@ -19,10 +19,13 @@ */ using System.ComponentModel.Composition; -using SonarLint.VisualStudio.ConnectedMode.Binding; +using System.Security; +using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; using SonarLint.VisualStudio.SLCore.Listener.Credentials; +using SonarQube.Client.Helpers; namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation { @@ -33,12 +36,12 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation [PartCreationPolicy(CreationPolicy.Shared)] internal class CredentialsListener : ICredentialsListener { - private readonly ICredentialProvider credentialProvider; + private readonly ISolutionBindingCredentialsLoader credentialsLoader; [ImportingConstructor] - public CredentialsListener(ICredentialProvider credentialProvider) + public CredentialsListener(ISolutionBindingCredentialsLoader credentialsLoader) { - this.credentialProvider = credentialProvider; + this.credentialsLoader = credentialsLoader; } public Task GetCredentialsAsync(GetCredentialsParams parameters) @@ -49,16 +52,25 @@ public Task GetCredentialsAsync(GetCredentialsParams par } var serverUri = new Uri(parameters.connectionId); - var credentials = credentialProvider.GetCredentials(serverUri); + var credentials = credentialsLoader.Load(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))); + if (credentials is ITokenCredentials tokenCredentials) + { + return Task.FromResult(new GetCredentialsResponse(new TokenDto(tokenCredentials.Token.ToUnsecureString()))); + } + else if (credentials is IUsernameAndPasswordCredentials usernamePasswordCredentials) + { + return Task.FromResult(new GetCredentialsResponse(new UsernamePasswordDto( + usernamePasswordCredentials.UserName, + usernamePasswordCredentials.Password.ToUnsecureString()))); + } + + return Task.FromResult(GetCredentialsResponse.NoCredentials); } } } diff --git a/src/SLCore.Listeners/packages.lock.json b/src/SLCore.Listeners/packages.lock.json index 8d168e41ae..40c45ee7d4 100644 --- a/src/SLCore.Listeners/packages.lock.json +++ b/src/SLCore.Listeners/packages.lock.json @@ -1015,6 +1015,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1156,7 +1161,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/SLCore.UnitTests/packages.lock.json b/src/SLCore.UnitTests/packages.lock.json index 33a58cfe9b..f9c6ce1f4a 100644 --- a/src/SLCore.UnitTests/packages.lock.json +++ b/src/SLCore.UnitTests/packages.lock.json @@ -1088,6 +1088,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1229,7 +1234,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { diff --git a/src/TestInfrastructure/packages.lock.json b/src/TestInfrastructure/packages.lock.json index 40fcb507bd..ac150b32d9 100644 --- a/src/TestInfrastructure/packages.lock.json +++ b/src/TestInfrastructure/packages.lock.json @@ -1094,6 +1094,11 @@ "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "9.0.7", + "contentHash": "OAiDZTyzIzgbtrjzbMlprCxvlXpFW7q+JVOSEc/v4jgLBF4hVSey0MQ06MyctGjspKyJBdGj6k6MuqjiZV9c5Q==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1235,7 +1240,8 @@ "SonarLint.VisualStudio.Core": "[1.0.0, )", "SonarLint.VisualStudio.IssueVisualization": "[1.0.0, )", "SonarLint.VisualStudio.SLCore": "[1.0.0, )", - "StrongNamer": "[0.0.8, )" + "StrongNamer": "[0.0.8, )", + "System.Security.Cryptography.ProtectedData": "[9.0.7, )" } }, "SonarLint.VisualStudio.Core": { From d4c44cd899ef9d90a927fc9728f9b89d7d37f4df Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 11 Jul 2025 15:45:52 +0200 Subject: [PATCH 2/3] ii --- src/ConnectedMode/ConnectedMode.csproj | 7 +++ .../CredentialStore2/CredentialStore2.cs | 39 +++++++++--- .../MasterPasswordDialog.xaml | 43 +++++++++++++ .../MasterPasswordDialog.xaml.cs | 62 +++++++++++++++++++ 4 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 src/ConnectedMode/CredentialStore2/MasterPasswordDialog.xaml create mode 100644 src/ConnectedMode/CredentialStore2/MasterPasswordDialog.xaml.cs diff --git a/src/ConnectedMode/ConnectedMode.csproj b/src/ConnectedMode/ConnectedMode.csproj index 8b6a7b311e..0290fbff56 100644 --- a/src/ConnectedMode/ConnectedMode.csproj +++ b/src/ConnectedMode/ConnectedMode.csproj @@ -93,6 +93,7 @@ + @@ -175,6 +176,12 @@ + + + MSBuild:Compile + + + diff --git a/src/ConnectedMode/CredentialStore2/CredentialStore2.cs b/src/ConnectedMode/CredentialStore2/CredentialStore2.cs index ac33a0fff6..a6a90659df 100644 --- a/src/ConnectedMode/CredentialStore2/CredentialStore2.cs +++ b/src/ConnectedMode/CredentialStore2/CredentialStore2.cs @@ -23,8 +23,10 @@ using System.Security; using System.Security.Cryptography; using System.Text; +using System.Windows; using Newtonsoft.Json; using SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Core.Synchronization; using SonarLint.VisualStudio.Core.SystemAbstractions; @@ -44,22 +46,17 @@ public class CredentialStore2 : ISolutionBindingCredentialsLoader, IDisposable { private readonly IFileSystemService fileSystem; private readonly IAsyncLock asyncLock; + private readonly IThreadHandling threadHandling; private readonly string storagePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SLVS_Credentials", "credentials.json"); - private readonly SecureString masterPassword; + private SecureString masterPassword; private bool disposed = false; [ImportingConstructor] - public CredentialStore2(IFileSystemService fileSystem, IAsyncLockFactory asyncLockFactory) + public CredentialStore2(IFileSystemService fileSystem, IAsyncLockFactory asyncLockFactory, IThreadHandling threadHandling) { this.fileSystem = fileSystem; + this.threadHandling = threadHandling; asyncLock = asyncLockFactory.Create(); - - masterPassword = new SecureString(); - foreach (char c in "testpassword") - { - masterPassword.AppendChar(c); - } - masterPassword.MakeReadOnly(); } public void DeleteCredentials(Uri targetUri) @@ -171,6 +168,13 @@ private byte[] UseMasterPasswordSafe(Func operation) byte[] result = null; try { + EnsureMasterPasswordInitialized(); + + if (masterPassword == null || masterPassword.Length == 0) + { + throw new InvalidOperationException("Master password is required but was not provided"); + } + masterPasswordUnprotectedBytes = Encoding.UTF8.GetBytes(masterPassword.ToUnsecureString()); result = operation(masterPasswordUnprotectedBytes); } @@ -181,6 +185,23 @@ private byte[] UseMasterPasswordSafe(Func operation) return result; } + private void EnsureMasterPasswordInitialized() + { + if (masterPassword == null || masterPassword.Length == 0) + { + threadHandling.RunOnUIThread(() => + { + var dialog = new MasterPasswordDialog(); + var dialogResult = dialog.ShowDialog(); + + if (dialogResult.HasValue && dialogResult.Value) + { + masterPassword = dialog.MasterPassword; + } + }); + } + } + private string ReadToken(Uri targetUri) { if (fileSystem.File.Exists(storagePath)) diff --git a/src/ConnectedMode/CredentialStore2/MasterPasswordDialog.xaml b/src/ConnectedMode/CredentialStore2/MasterPasswordDialog.xaml new file mode 100644 index 0000000000..a6190e55f6 --- /dev/null +++ b/src/ConnectedMode/CredentialStore2/MasterPasswordDialog.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + +