From 0d86248b4e777d3ae6b781facaa26caf70c76945 Mon Sep 17 00:00:00 2001 From: firedragon Date: Tue, 1 Sep 2026 15:46:17 -0300 Subject: [PATCH 1/2] fix: improve broker authentication handling --- Backup/Conviso.Platform.VisualStudio.sln | 25 ++ Conviso.Platform.VisualStudio.sln | 1 - UpgradeLog.htm | 275 ++++++++++++++++++ .../AssemblyInfo.cs | 4 +- .../Services/Broker/BrokerClient.cs | 80 +++-- .../source.extension.vsixmanifest | 2 +- 6 files changed, 363 insertions(+), 24 deletions(-) create mode 100644 Backup/Conviso.Platform.VisualStudio.sln create mode 100644 UpgradeLog.htm diff --git a/Backup/Conviso.Platform.VisualStudio.sln b/Backup/Conviso.Platform.VisualStudio.sln new file mode 100644 index 0000000..594cb50 --- /dev/null +++ b/Backup/Conviso.Platform.VisualStudio.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Conviso.Platform.VisualStudio", "src\Conviso.Platform.VisualStudio\Conviso.Platform.VisualStudio.csproj", "{60E0A468-A01B-4C56-9AC9-9B2FCD90B20E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60E0A468-A01B-4C56-9AC9-9B2FCD90B20E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60E0A468-A01B-4C56-9AC9-9B2FCD90B20E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60E0A468-A01B-4C56-9AC9-9B2FCD90B20E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60E0A468-A01B-4C56-9AC9-9B2FCD90B20E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4F23F0B7-B5F8-42D2-A711-F725B2C5B7E2} + EndGlobalSection +EndGlobal diff --git a/Conviso.Platform.VisualStudio.sln b/Conviso.Platform.VisualStudio.sln index 594cb50..ef6d827 100644 --- a/Conviso.Platform.VisualStudio.sln +++ b/Conviso.Platform.VisualStudio.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 0000000..256b186 --- /dev/null +++ b/UpgradeLog.htm @@ -0,0 +1,275 @@ + + + + Relatório de Migração +

+ Relatório de Migração -

Visão geral

ProjetoCaminhoErrosAvisosMensagens
SoluçãoConviso.Platform.VisualStudio.sln012
Conviso.Platform.VisualStudiosrc\Conviso.Platform.VisualStudio\Conviso.Platform.VisualStudio.csproj000

Solução e projetos

\ No newline at end of file diff --git a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs index 21bed72..03adedb 100644 --- a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs +++ b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs @@ -5,6 +5,6 @@ [assembly: AssemblyDescription("Conviso Platform Visual Studio extension")] [assembly: AssemblyCompany("Conviso")] [assembly: AssemblyProduct("Conviso Platform Visual Studio")] -[assembly: AssemblyVersion("0.2.1.0")] -[assembly: AssemblyFileVersion("0.2.1.0")] +[assembly: AssemblyVersion("0.2.2.0")] +[assembly: AssemblyFileVersion("0.2.2.0")] [assembly: ComVisible(false)] diff --git a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs index e30a051..8f84ae3 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs @@ -15,16 +15,18 @@ internal sealed class BrokerClient : IBrokerClient private ClientWebSocket? socket; private CancellationTokenSource? receiveLoopCancellation; private TaskCompletionSource? authenticationCompletionSource; + private volatile bool isAuthenticated; private readonly object exclusiveRequestsLock = new object(); private readonly HashSet exclusiveRequestIds = new HashSet(StringComparer.Ordinal); private event Action? InternalEventReceived; public event Action? EventReceived; - public bool IsConnected => socket != null && socket.State == WebSocketState.Open; + public bool IsConnected => isAuthenticated && socket != null && socket.State == WebSocketState.Open; public async Task ConnectAsync(BrokerConnectionOptions options, CancellationToken cancellationToken) { + isAuthenticated = false; await DisconnectAsync(cancellationToken); socket = new ClientWebSocket(); @@ -35,7 +37,8 @@ public async Task ConnectAsync(BrokerConnectionOptions options, CancellationToke throw new InvalidOperationException("Missing chat API key."); } - authenticationCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var authenticationCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + authenticationCompletionSource = authenticationCompletion; await socket.ConnectAsync(new Uri(endpoint), cancellationToken); receiveLoopCancellation = new CancellationTokenSource(); @@ -47,7 +50,7 @@ public async Task ConnectAsync(BrokerConnectionOptions options, CancellationToke string authRequestId = CreateRequestId("auth"); await SendMessageAsync( - socket, + activeSocket, new { type = "auth", @@ -64,11 +67,11 @@ await SendMessageAsync( cancellationToken, timeoutCancellation.Token); - using (linkedCancellation.Token.Register(() => authenticationCompletionSource.TrySetCanceled(), useSynchronizationContext: false)) + using (linkedCancellation.Token.Register(() => authenticationCompletion.TrySetCanceled(), useSynchronizationContext: false)) { try { - await authenticationCompletionSource.Task; + await authenticationCompletion.Task; } catch (TaskCanceledException) when (timeoutCancellation.IsCancellationRequested) { @@ -83,14 +86,11 @@ await SendMessageAsync( public async Task SendChatMessageAsync(ChatMessage message, CancellationToken cancellationToken) { - if (socket == null || socket.State != WebSocketState.Open) - { - throw new InvalidOperationException("Broker is not connected."); - } + ClientWebSocket activeSocket = GetAuthenticatedSocket(); string requestId = CreateRequestId("req"); await SendMessageAsync( - socket, + activeSocket, new { type = "analyze_code", @@ -107,10 +107,7 @@ await SendMessageAsync( public async Task RequestAutoFixAsync(string findingId, CancellationToken cancellationToken) { - if (socket == null || socket.State != WebSocketState.Open) - { - throw new InvalidOperationException("Broker is not connected."); - } + ClientWebSocket activeSocket = GetAuthenticatedSocket(); if (string.IsNullOrWhiteSpace(findingId)) { @@ -153,7 +150,7 @@ void HandleEvent(BrokerEvent brokerEvent) try { await SendMessageAsync( - socket, + activeSocket, new { type = "analyze_code", @@ -223,10 +220,7 @@ await SendMessageAsync( public async Task UpdateExtractorAcceptedAsync(int extractorId, CancellationToken cancellationToken) { - if (socket == null || socket.State != WebSocketState.Open) - { - throw new InvalidOperationException("Broker is not connected."); - } + ClientWebSocket activeSocket = GetAuthenticatedSocket(); if (extractorId <= 0) { @@ -263,7 +257,7 @@ void HandleEvent(BrokerEvent brokerEvent) try { await SendMessageAsync( - socket, + activeSocket, new { type = "update_extractor", @@ -302,6 +296,7 @@ await SendMessageAsync( public async Task DisconnectAsync(CancellationToken cancellationToken) { + isAuthenticated = false; authenticationCompletionSource?.TrySetCanceled(); authenticationCompletionSource = null; receiveLoopCancellation?.Cancel(); @@ -352,6 +347,7 @@ private async Task ReceiveLoopAsync(ClientWebSocket activeSocket, CancellationTo result = await activeSocket.ReceiveAsync(new ArraySegment(buffer), cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { + isAuthenticated = false; authenticationCompletionSource?.TrySetException( new InvalidOperationException("Chat connection closed before authentication completed.")); return; @@ -377,6 +373,7 @@ private async Task RunReceiveLoopSafelyAsync(ClientWebSocket activeSocket, Cance } catch (Exception error) { + isAuthenticated = false; authenticationCompletionSource?.TrySetException(error); Infrastructure.DiagnosticsLogger.LogError("Chat receive loop stopped: " + error); } @@ -390,11 +387,13 @@ private void ProcessIncomingMessage(string raw) { if (brokerEvent.Status == "success") { + isAuthenticated = true; authenticationCompletionSource?.TrySetResult(true); authenticationCompletionSource = null; } else { + isAuthenticated = false; authenticationCompletionSource?.TrySetException( new InvalidOperationException(string.IsNullOrWhiteSpace(brokerEvent.Content) ? "Chat authentication failed." @@ -407,6 +406,7 @@ private void ProcessIncomingMessage(string raw) if (brokerEvent.Type == "auth_error") { + isAuthenticated = false; authenticationCompletionSource?.TrySetException( new InvalidOperationException(string.IsNullOrWhiteSpace(brokerEvent.Content) ? "Chat authentication failed." @@ -415,6 +415,15 @@ private void ProcessIncomingMessage(string raw) return; } + if ((brokerEvent.Type == "error" || brokerEvent.Type == "analysis_error") && + IsAuthenticationFailure(brokerEvent.Content)) + { + // The server can keep the socket open after the authenticated session + // expires. Mark it unusable so the next operation performs a full + // reconnect and authentication handshake. + isAuthenticated = false; + } + InternalEventReceived?.Invoke(brokerEvent); bool isExclusiveRequest; @@ -495,6 +504,37 @@ private static BrokerEvent ParseEvent(string raw) return new BrokerEvent(type, requestId, content, raw, status); } + private static bool IsAuthenticationFailure(string message) + { + if (string.IsNullOrWhiteSpace(message)) + { + return false; + } + + bool mentionsPortugueseAuthentication = + (message.IndexOf("conex", StringComparison.OrdinalIgnoreCase) >= 0 || + message.IndexOf("connection", StringComparison.OrdinalIgnoreCase) >= 0) && + message.IndexOf("autenticad", StringComparison.OrdinalIgnoreCase) >= 0; + + return message.IndexOf("not authenticated", StringComparison.OrdinalIgnoreCase) >= 0 || + message.IndexOf("unauthenticated", StringComparison.OrdinalIgnoreCase) >= 0 || + message.IndexOf("authentication required", StringComparison.OrdinalIgnoreCase) >= 0 || + message.IndexOf("unauthorized", StringComparison.OrdinalIgnoreCase) >= 0 || + message.IndexOf("nao autentic", StringComparison.OrdinalIgnoreCase) >= 0 || + mentionsPortugueseAuthentication; + } + + private ClientWebSocket GetAuthenticatedSocket() + { + ClientWebSocket? activeSocket = socket; + if (!isAuthenticated || activeSocket == null || activeSocket.State != WebSocketState.Open) + { + throw new InvalidOperationException("Broker is not authenticated."); + } + + return activeSocket; + } + private static async Task SendMessageAsync( ClientWebSocket socket, object payload, diff --git a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest index ccee98f..6be77c8 100644 --- a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest +++ b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Conviso Platform Conviso Platform integration for Visual Studio. Resources\pluginIcon.png From b113d794446ea17d77f76958191550247e5e20b5 Mon Sep 17 00:00:00 2001 From: firedragon Date: Tue, 1 Sep 2026 15:52:17 -0300 Subject: [PATCH 2/2] feat: pass company ID in code analysis requests --- .../AssemblyInfo.cs | 4 ++-- .../ConvisoPlatformPackage.cs | 2 +- .../Services/Broker/BrokerClient.cs | 20 +++++++++++++++++++ .../ViewModels/SettingsToolWindowViewModel.cs | 2 +- .../source.extension.vsixmanifest | 2 +- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs index 03adedb..cfb8345 100644 --- a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs +++ b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs @@ -5,6 +5,6 @@ [assembly: AssemblyDescription("Conviso Platform Visual Studio extension")] [assembly: AssemblyCompany("Conviso")] [assembly: AssemblyProduct("Conviso Platform Visual Studio")] -[assembly: AssemblyVersion("0.2.2.0")] -[assembly: AssemblyFileVersion("0.2.2.0")] +[assembly: AssemblyVersion("0.2.3.0")] +[assembly: AssemblyFileVersion("0.2.3.0")] [assembly: ComVisible(false)] diff --git a/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs b/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs index 3fb0342..663ed71 100644 --- a/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs +++ b/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs @@ -38,7 +38,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke var settingsService = new SettingsService(this); var apiClient = new PlatformApiClient(settingsService); var platformFacade = new PlatformFacade(apiClient, settingsService); - var brokerClient = new BrokerClient(); + var brokerClient = new BrokerClient(settingsService); var editorContextService = new EditorContextService(this); var patchService = new DocumentPatchService(this); ToolWindowContext = new ToolWindowContext(settingsService, platformFacade, brokerClient, editorContextService, patchService); diff --git a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs index 8f84ae3..b5643fa 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Conviso.Platform.VisualStudio.Configuration; using Conviso.Platform.VisualStudio.Models; namespace Conviso.Platform.VisualStudio.Services.Broker @@ -12,6 +13,7 @@ namespace Conviso.Platform.VisualStudio.Services.Broker internal sealed class BrokerClient : IBrokerClient { private const int ConnectTimeoutMilliseconds = 15000; + private readonly ISettingsService settingsService; private ClientWebSocket? socket; private CancellationTokenSource? receiveLoopCancellation; private TaskCompletionSource? authenticationCompletionSource; @@ -22,6 +24,11 @@ internal sealed class BrokerClient : IBrokerClient public event Action? EventReceived; + public BrokerClient(ISettingsService settingsService) + { + this.settingsService = settingsService; + } + public bool IsConnected => isAuthenticated && socket != null && socket.State == WebSocketState.Open; public async Task ConnectAsync(BrokerConnectionOptions options, CancellationToken cancellationToken) @@ -99,6 +106,7 @@ await SendMessageAsync( { code = message.Content, language = string.IsNullOrWhiteSpace(message.Language) ? "text" : message.Language, + company_id = GetCompanyId(), }, }, cancellationToken); @@ -163,6 +171,7 @@ await SendMessageAsync( "Explain the risk and provide the corrected code in a fenced code block when possible.", "Vulnerability ID: " + findingId), language = "text", + company_id = GetCompanyId(), }, }, cancellationToken); @@ -535,6 +544,17 @@ private ClientWebSocket GetAuthenticatedSocket() return activeSocket; } + private int GetCompanyId() + { + string companyId = settingsService.GetString(ConvisoOptions.CompanyIdKey, string.Empty); + if (!int.TryParse(companyId, out int numericCompanyId)) + { + throw new InvalidOperationException("Configure a valid numeric Company ID before analyzing code."); + } + + return numericCompanyId; + } + private static async Task SendMessageAsync( ClientWebSocket socket, object payload, diff --git a/src/Conviso.Platform.VisualStudio/ViewModels/SettingsToolWindowViewModel.cs b/src/Conviso.Platform.VisualStudio/ViewModels/SettingsToolWindowViewModel.cs index f533f23..9ea606f 100644 --- a/src/Conviso.Platform.VisualStudio/ViewModels/SettingsToolWindowViewModel.cs +++ b/src/Conviso.Platform.VisualStudio/ViewModels/SettingsToolWindowViewModel.cs @@ -129,7 +129,7 @@ private async Task TestApiAsync() private async Task TestBrokerAsync() { - var brokerClient = new BrokerClient(); + var brokerClient = new BrokerClient(settingsService); try { Status = "Testing broker..."; diff --git a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest index 6be77c8..06c9b01 100644 --- a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest +++ b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Conviso Platform Conviso Platform integration for Visual Studio. Resources\pluginIcon.png