diff --git a/CHANGELOG.md b/CHANGELOG.md index 06bc2815..bc995c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added -- The chat view style now updates to match the theme of the editor on theme change. [pull/85](https://github.com/sourcegraph/cody-vs/pull/85) +- Chat view style now matches the editor theme on theme changes. [pull/85](https://github.com/sourcegraph/cody-vs/pull/85) +- Added browser-based authentication support. [pull/84](https://github.com/sourcegraph/cody-vs/pull/84) ### Changed diff --git a/src/Cody.AgentTester/Cody.AgentTester.csproj b/src/Cody.AgentTester/Cody.AgentTester.csproj index ef5ca956..cb9b39b5 100644 --- a/src/Cody.AgentTester/Cody.AgentTester.csproj +++ b/src/Cody.AgentTester/Cody.AgentTester.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Cody.AgentTester/FakeSecretStorageProvider.cs b/src/Cody.AgentTester/FakeSecretStorageProvider.cs new file mode 100644 index 00000000..5e9c364e --- /dev/null +++ b/src/Cody.AgentTester/FakeSecretStorageProvider.cs @@ -0,0 +1,81 @@ +using Microsoft.VisualStudio.Shell.Connected.CredentialStorage; +using System; +using System.Collections.Generic; + +namespace Cody.AgentTester +{ + public class FakeSecretStorageProvider : IVsCredentialStorageService + { + private Dictionary _credentials = new Dictionary(); + + public IVsCredential Add(IVsCredentialKey key, string value) + { + var credential = new FakeCredential(value); + _credentials[key] = credential; + return credential; + } + public IVsCredential Retrieve(IVsCredentialKey key) + { + return _credentials[key]; + } + public IEnumerable RetrieveAll(string key) + { + throw new NotImplementedException(); + } + public bool Remove(IVsCredentialKey key) + { + return _credentials.Remove(key); + } + public IVsCredentialKey CreateCredentialKey(string featureName, string resource, string userName, string type) + { + return new FakeCredentialKey(featureName, resource, userName, type); + } + + private class FakeCredentialKey : IVsCredentialKey + { + public string FeatureName { get; set; } + public string UserName { get; set; } + public string Type { get; set; } + public string Resource { get; set; } + + public FakeCredentialKey(string featureName, string resource, string userName, string type) + { + FeatureName = featureName; + UserName = userName; + Type = type; + Resource = resource; + } + } + + private class FakeCredential : IVsCredential + { + public string FeatureName { get; set; } + public string UserName { get; set; } + public string Type { get; set; } + public string Resource { get; set; } + + public string TokenValue { get; set; } + + public bool RefreshTokenValue() + { + return true; + } + public void SetTokenValue(string tokenValue) + { + TokenValue = tokenValue; + } + public string GetProperty(string name) + { + return name; + } + public bool SetProperty(string name, string value) + { + return true; + } + public FakeCredential(string tokenValue) + { + TokenValue = tokenValue; + } + } + } +} diff --git a/src/Cody.AgentTester/Program.cs b/src/Cody.AgentTester/Program.cs index dbdf313d..3ca68f28 100644 --- a/src/Cody.AgentTester/Program.cs +++ b/src/Cody.AgentTester/Program.cs @@ -30,11 +30,12 @@ static async Task Main(string[] args) var portNumber = int.TryParse(devPort, out int port) ? port : 3113; var logger = new Logger(); - var settingsService = new UserSettingsService(new MemorySettingsProvider(), logger); + var secretStorageService = new SecretStorageService(new FakeSecretStorageProvider()); + var settingsService = new UserSettingsService(new MemorySettingsProvider(), secretStorageService, logger); var editorService = new FileService(new FakeServiceProvider(), logger); var options = new AgentClientOptions { - CallbackHandlers = new List { new NotificationHandlers(settingsService, logger, editorService) }, + CallbackHandlers = new List { new NotificationHandlers(settingsService, logger, editorService, secretStorageService) }, AgentDirectory = "../../../Cody.VisualStudio/Agent", RestartAgentOnFailure = true, Debug = true, @@ -63,6 +64,7 @@ private static async Task Initialize() WorkspaceRootUri = Directory.GetCurrentDirectory().ToString(), Capabilities = new ClientCapabilities { + Authentication = Capability.Enabled, Edit = Capability.Enabled, EditWorkspace = Capability.None, CodeLenses = Capability.None, @@ -78,6 +80,7 @@ private static async Task Initialize() }, WebviewMessages = "string-encoded", GlobalState = "stateless", + Secrets = "stateless", }, ExtensionConfiguration = new ExtensionConfiguration { diff --git a/src/Cody.Core/Agent/InitializeCallback.cs b/src/Cody.Core/Agent/InitializeCallback.cs index 14c6ff6b..2d2a3312 100644 --- a/src/Cody.Core/Agent/InitializeCallback.cs +++ b/src/Cody.Core/Agent/InitializeCallback.cs @@ -43,6 +43,7 @@ public async Task Initialize(IAgentService client) WorkspaceRootUri = solutionService.GetSolutionDirectory(), Capabilities = new ClientCapabilities { + Authentication = Capability.Enabled, Completions = "none", Edit = Capability.None, EditWorkspace = Capability.None, @@ -60,6 +61,7 @@ public async Task Initialize(IAgentService client) }, WebviewMessages = "string-encoded", GlobalState = "server-managed", + Secrets = "client-managed", }, ExtensionConfiguration = GetConfiguration() }; diff --git a/src/Cody.Core/Agent/NotificationHandlers.cs b/src/Cody.Core/Agent/NotificationHandlers.cs index 0c1a52a8..23967097 100644 --- a/src/Cody.Core/Agent/NotificationHandlers.cs +++ b/src/Cody.Core/Agent/NotificationHandlers.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json.Linq; using System; using System.Threading.Tasks; +using Cody.Core.Infrastructure; namespace Cody.Core.Agent { @@ -13,6 +14,7 @@ public class NotificationHandlers : INotificationHandler private readonly WebviewMessageHandler _messageFilter; private readonly IUserSettingsService _settingsService; private readonly IFileService _fileService; + private readonly ISecretStorageService _secretStorage; private readonly ILog _logger; public IAgentService agentClient; @@ -27,10 +29,11 @@ public class NotificationHandlers : INotificationHandler public event EventHandler OnPostMessageEvent; - public NotificationHandlers(IUserSettingsService settingsService, ILog logger, IFileService fileService) + public NotificationHandlers(IUserSettingsService settingsService, ILog logger, IFileService fileService, ISecretStorageService secretStorage) { _settingsService = settingsService; _fileService = fileService; + _secretStorage = secretStorage; _logger = logger; _messageFilter = new WebviewMessageHandler(settingsService, fileService, () => OnOptionsPageShowRequest?.Invoke(this, EventArgs.Empty)); } @@ -188,5 +191,26 @@ public Task ShowSaveDialog(SaveDialogOptionsParams paramValues) { return Task.FromResult("Not Yet Implemented"); } + + [AgentCallback("secrets/get")] + public Task SecretGet(string key) + { + _logger.Debug(key, $@"SecretGet - {key}"); + return Task.FromResult(_secretStorage.Get(key)); + } + + [AgentCallback("secrets/store")] + public void SecretStore(string key, string value) + { + _logger.Debug(key, $@"SecretStore - {key}"); + _secretStorage.Set(key, value); + } + + [AgentCallback("secrets/delete")] + public void SecretDelete(string key) + { + _logger.Debug(key, $@"SecretDelete - {key}"); + _secretStorage.Delete(key); + } } } diff --git a/src/Cody.Core/Agent/Protocol/ClientCapabilities.cs b/src/Cody.Core/Agent/Protocol/ClientCapabilities.cs index 1589c71d..cd81f491 100644 --- a/src/Cody.Core/Agent/Protocol/ClientCapabilities.cs +++ b/src/Cody.Core/Agent/Protocol/ClientCapabilities.cs @@ -18,6 +18,8 @@ public class ClientCapabilities public string Webview { get; set; } // 'agentic' | 'native' public WebviewCapabilities WebviewNativeConfig { get; set; } public string GlobalState { get; set; } // 'stateless' | 'server-managed' | 'client-managed' + public string Secrets { get; set; } // 'stateless' | 'server-managed' | 'client-managed' + public Capability? Authentication { get; set; } } public enum Capability diff --git a/src/Cody.Core/Cody.Core.csproj b/src/Cody.Core/Cody.Core.csproj index edabc2a8..cdb44428 100644 --- a/src/Cody.Core/Cody.Core.csproj +++ b/src/Cody.Core/Cody.Core.csproj @@ -87,6 +87,7 @@ + diff --git a/src/Cody.Core/Infrastructure/ISecretStorageService.cs b/src/Cody.Core/Infrastructure/ISecretStorageService.cs new file mode 100644 index 00000000..e4aae4bf --- /dev/null +++ b/src/Cody.Core/Infrastructure/ISecretStorageService.cs @@ -0,0 +1,10 @@ +namespace Cody.Core.Infrastructure +{ + public interface ISecretStorageService + { + void Set(string key, string value); + string Get(string key); + void Delete(string key); + string AccessToken { get; set; } + } +} diff --git a/src/Cody.Core/Infrastructure/ISolutionService.cs b/src/Cody.Core/Infrastructure/ISolutionService.cs index 56e3e06c..118b4b99 100644 --- a/src/Cody.Core/Infrastructure/ISolutionService.cs +++ b/src/Cody.Core/Infrastructure/ISolutionService.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Cody.Core.Infrastructure { public interface ISolutionService diff --git a/src/Cody.Core/Settings/IUserSettingsService.cs b/src/Cody.Core/Settings/IUserSettingsService.cs index 41ba7efe..b24d0cf3 100644 --- a/src/Cody.Core/Settings/IUserSettingsService.cs +++ b/src/Cody.Core/Settings/IUserSettingsService.cs @@ -8,6 +8,7 @@ public interface IUserSettingsService string AccessToken { get; set; } string ServerEndpoint { get; set; } + string CodySettings { get; set; } event EventHandler AuthorizationDetailsChanged; } } diff --git a/src/Cody.Core/Settings/UserSettingsService.cs b/src/Cody.Core/Settings/UserSettingsService.cs index 3bafcf26..49c109a9 100644 --- a/src/Cody.Core/Settings/UserSettingsService.cs +++ b/src/Cody.Core/Settings/UserSettingsService.cs @@ -1,3 +1,4 @@ +using Cody.Core.Infrastructure; using Cody.Core.Logging; using System; @@ -6,13 +7,15 @@ namespace Cody.Core.Settings public class UserSettingsService : IUserSettingsService { private readonly IUserSettingsProvider _settingsProvider; + private readonly ISecretStorageService _secretStorage; private readonly ILog _logger; public event EventHandler AuthorizationDetailsChanged; - public UserSettingsService(IUserSettingsProvider settingsProvider, ILog log) + public UserSettingsService(IUserSettingsProvider settingsProvider, ISecretStorageService secretStorage, ILog log) { _settingsProvider = settingsProvider; + _secretStorage = secretStorage; _logger = log; } @@ -71,25 +74,32 @@ public string AccessToken get { var envToken = Environment.GetEnvironmentVariable("SourcegraphCodyToken"); - var userToken = GetOrDefault(nameof(AccessToken)); - ; + var userToken = _secretStorage.AccessToken; + if (envToken != null && userToken == null) // use env token only when a user token is not set { _logger.Warn("You are using a access token from environment variables!"); return envToken; } - return GetOrDefault(nameof(AccessToken)); + return userToken; } set { - var token = GetOrDefault(nameof(AccessToken)); - if (!string.Equals(value, token, StringComparison.InvariantCulture)) + var userToken = _secretStorage.AccessToken; + if (!string.Equals(value, userToken, StringComparison.InvariantCulture)) { - Set(nameof(AccessToken), value); + _secretStorage.AccessToken = value; AuthorizationDetailsChanged?.Invoke(this, EventArgs.Empty); } } } + + + public string CodySettings + { + get => GetOrDefault(nameof(CodySettings), string.Empty); + set => Set(nameof(CodySettings), value); + } } } diff --git a/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml b/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml index c5d49f07..c4b50a3d 100644 --- a/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml +++ b/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml @@ -1,4 +1,4 @@ - - +