Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement authentication and secret storage capability #84

Merged
merged 8 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/Cody.AgentTester/Cody.AgentTester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<ItemGroup>
<Compile Include="AssemblyLoader.cs" />
<Compile Include="ConsoleLogger.cs" />
<Compile Include="FakeSecretStorageProvider.cs" />
<Compile Include="FakeServiceProvider.cs" />
<Compile Include="MemorySettingsProvider.cs" />
<Compile Include="Program.cs" />
Expand Down
81 changes: 81 additions & 0 deletions src/Cody.AgentTester/FakeSecretStorageProvider.cs
Original file line number Diff line number Diff line change
@@ -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<IVsCredentialKey, IVsCredential> _credentials = new Dictionary<IVsCredentialKey, IVsCredential>();

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<IVsCredential> 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;
}
}
}
}
7 changes: 5 additions & 2 deletions src/Cody.AgentTester/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object> { new NotificationHandlers(settingsService, logger, editorService) },
CallbackHandlers = new List<object> { new NotificationHandlers(settingsService, logger, editorService, secretStorageService) },
AgentDirectory = "../../../Cody.VisualStudio/Agent",
RestartAgentOnFailure = true,
Debug = true,
Expand Down Expand Up @@ -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,
Expand All @@ -78,6 +80,7 @@ private static async Task Initialize()
},
WebviewMessages = "string-encoded",
GlobalState = "stateless",
Secrets = "stateless",
},
ExtensionConfiguration = new ExtensionConfiguration
{
Expand Down
2 changes: 2 additions & 0 deletions src/Cody.Core/Agent/InitializeCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -60,6 +61,7 @@ public async Task Initialize(IAgentService client)
},
WebviewMessages = "string-encoded",
GlobalState = "server-managed",
Secrets = "client-managed",
},
ExtensionConfiguration = GetConfiguration()
};
Expand Down
26 changes: 25 additions & 1 deletion src/Cody.Core/Agent/NotificationHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Newtonsoft.Json.Linq;
using System;
using System.Threading.Tasks;
using Cody.Core.Infrastructure;

namespace Cody.Core.Agent
{
Expand All @@ -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;
Expand All @@ -27,10 +29,11 @@ public class NotificationHandlers : INotificationHandler

public event EventHandler<AgentResponseEvent> 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));
}
Expand Down Expand Up @@ -188,5 +191,26 @@ public Task<string> ShowSaveDialog(SaveDialogOptionsParams paramValues)
{
return Task.FromResult("Not Yet Implemented");
}

[AgentCallback("secrets/get")]
public Task<string> 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);
}
}
}
2 changes: 2 additions & 0 deletions src/Cody.Core/Agent/Protocol/ClientCapabilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/Cody.Core/Cody.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<Compile Include="Agent\WebviewMessageHandler.cs" />
<Compile Include="DocumentSync\DocumentSyncCallback.cs" />
<Compile Include="DocumentSync\IDocumentSyncActions.cs" />
<Compile Include="Infrastructure\ISecretStorageService.cs" />
<Compile Include="Infrastructure\IProgressService.cs" />
<Compile Include="Workspace\IFileService.cs" />
<Compile Include="Ide\IVsVersionService.cs" />
Expand Down
10 changes: 10 additions & 0 deletions src/Cody.Core/Infrastructure/ISecretStorageService.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
6 changes: 0 additions & 6 deletions src/Cody.Core/Infrastructure/ISolutionService.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions src/Cody.Core/Settings/IUserSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface IUserSettingsService

string AccessToken { get; set; }
string ServerEndpoint { get; set; }
string CodySettings { get; set; }
event EventHandler AuthorizationDetailsChanged;
}
}
24 changes: 17 additions & 7 deletions src/Cody.Core/Settings/UserSettingsService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Cody.Core.Infrastructure;
using Cody.Core.Logging;
using System;

Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}
}
}
26 changes: 13 additions & 13 deletions src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<UserControl x:Class="Cody.UI.Controls.Options.GeneralOptionsControl"
<UserControl x:Class="Cody.UI.Controls.Options.GeneralOptionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Expand All @@ -25,45 +25,45 @@
</Grid.ColumnDefinitions>


<!--Access Token-->
<!--Cody Configurations-->
<Label
Grid.Row="0"
Grid.Row="1"
Grid.Column="0"
Content="Access Token"
Content="Cody Settings"
/>

<TextBox
Grid.Row="0"
Grid.Row="1"
Grid.Column="1"
Width="400"
Height="20"
Text="{Binding AccessToken, Mode=TwoWay}"
ToolTip="Paste your access token. To create an access token, go to 'Settings' and then 'Access tokens' on the Sourcegraph instance."
Name="AccessTokenTextBox"
Height="50"
Text="{Binding Configurations, Mode=TwoWay}"
ToolTip="Your Cody configuration JSON file."
Name="ConfigurationsTextBox"
/>

<!--Get a token-->
<!--Get Help-->
<Button
Margin="0 5 0 0"
Grid.Row="2"
Grid.Column="1"
Height="25"
Width="100"
Content="Get a token"
Content="Get Help"
HorizontalAlignment="Left"
Command="{Binding ActivateBetaCommand}"
/>

<!--Sourcegraph URL-->
<Label
Grid.Row="1"
Grid.Row="0"
Grid.Column="0"
Content="Sourcegraph URL"
/>

<TextBox
Name="SourcegraphUrlTextBox"
Grid.Row="1"
Grid.Row="0"
Grid.Column="1"
Width="400"
Height="20"
Expand Down
4 changes: 2 additions & 2 deletions src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows.Controls;
using System.Windows.Controls;

namespace Cody.UI.Controls.Options
{
Expand All @@ -16,8 +16,8 @@ public void ForceBindingsUpdate()
{
// TextBox binding doesn't work when Visual Studio closes Options window
// This is a workaround to get bindings updated. The second solution is to use NotifyPropertyChange for every TextBox in the Xaml, but current solution is a little more "clean" - everything is clearly visible in a one place.
AccessTokenTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
SourcegraphUrlTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
ConfigurationsTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
}
}
Loading
Loading