Move interaction with keychain off the main thread

This commit is contained in:
Antonio Scandurra 2024-01-24 14:53:05 +01:00
parent 22046ef9a7
commit 25a7eb27d2
12 changed files with 370 additions and 214 deletions

View file

@ -1,3 +1,4 @@
use futures::future::BoxFuture;
use gpui::AppContext;
#[derive(Clone, Debug)]
@ -9,7 +10,14 @@ pub enum ProviderCredential {
pub trait CredentialProvider: Send + Sync {
fn has_credentials(&self) -> bool;
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
fn delete_credentials(&self, cx: &mut AppContext);
#[must_use]
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential>;
#[must_use]
fn save_credentials(
&self,
cx: &mut AppContext,
credential: ProviderCredential,
) -> BoxFuture<()>;
#[must_use]
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()>;
}

View file

@ -222,45 +222,70 @@ impl CredentialProvider for OpenAICompletionProvider {
}
}
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
let existing_credential = self.credential.read().clone();
let retrieved_credential = match existing_credential {
ProviderCredential::Credentials { .. } => existing_credential.clone(),
ProviderCredential::Credentials { .. } => {
return async move { existing_credential }.boxed()
}
_ => {
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
ProviderCredential::Credentials { api_key }
} else if let Some(Some((_, api_key))) =
cx.read_credentials(OPENAI_API_URL).log_err()
{
if let Some(api_key) = String::from_utf8(api_key).log_err() {
ProviderCredential::Credentials { api_key }
} else {
ProviderCredential::NoCredentials
}
async move { ProviderCredential::Credentials { api_key } }.boxed()
} else {
ProviderCredential::NoCredentials
let credentials = cx.read_credentials(OPENAI_API_URL);
async move {
if let Some(Some((_, api_key))) = credentials.await.log_err() {
if let Some(api_key) = String::from_utf8(api_key).log_err() {
ProviderCredential::Credentials { api_key }
} else {
ProviderCredential::NoCredentials
}
} else {
ProviderCredential::NoCredentials
}
}
.boxed()
}
}
};
*self.credential.write() = retrieved_credential.clone();
retrieved_credential
async move {
let retrieved_credential = retrieved_credential.await;
*self.credential.write() = retrieved_credential.clone();
retrieved_credential
}
.boxed()
}
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
fn save_credentials(
&self,
cx: &mut AppContext,
credential: ProviderCredential,
) -> BoxFuture<()> {
*self.credential.write() = credential.clone();
let credential = credential.clone();
match credential {
let write_credentials = match credential {
ProviderCredential::Credentials { api_key } => {
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
.log_err();
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
}
_ => None,
};
async move {
if let Some(write_credentials) = write_credentials {
write_credentials.await.log_err();
}
_ => {}
}
.boxed()
}
fn delete_credentials(&self, cx: &mut AppContext) {
cx.delete_credentials(OPENAI_API_URL).log_err();
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
*self.credential.write() = ProviderCredential::NoCredentials;
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
async move {
delete_credentials.await.log_err();
}
.boxed()
}
}

View file

@ -1,6 +1,8 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::future::BoxFuture;
use futures::AsyncReadExt;
use futures::FutureExt;
use gpui::AppContext;
use gpui::BackgroundExecutor;
use isahc::http::StatusCode;
@ -157,46 +159,71 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
_ => false,
}
}
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
let existing_credential = self.credential.read().clone();
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
let existing_credential = self.credential.read().clone();
let retrieved_credential = match existing_credential {
ProviderCredential::Credentials { .. } => existing_credential.clone(),
ProviderCredential::Credentials { .. } => {
return async move { existing_credential }.boxed()
}
_ => {
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
ProviderCredential::Credentials { api_key }
} else if let Some(Some((_, api_key))) =
cx.read_credentials(OPENAI_API_URL).log_err()
{
if let Some(api_key) = String::from_utf8(api_key).log_err() {
ProviderCredential::Credentials { api_key }
} else {
ProviderCredential::NoCredentials
}
async move { ProviderCredential::Credentials { api_key } }.boxed()
} else {
ProviderCredential::NoCredentials
let credentials = cx.read_credentials(OPENAI_API_URL);
async move {
if let Some(Some((_, api_key))) = credentials.await.log_err() {
if let Some(api_key) = String::from_utf8(api_key).log_err() {
ProviderCredential::Credentials { api_key }
} else {
ProviderCredential::NoCredentials
}
} else {
ProviderCredential::NoCredentials
}
}
.boxed()
}
}
};
*self.credential.write() = retrieved_credential.clone();
retrieved_credential
}
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
*self.credential.write() = credential.clone();
match credential {
ProviderCredential::Credentials { api_key } => {
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
.log_err();
}
_ => {}
async move {
let retrieved_credential = retrieved_credential.await;
*self.credential.write() = retrieved_credential.clone();
retrieved_credential
}
.boxed()
}
fn delete_credentials(&self, cx: &mut AppContext) {
cx.delete_credentials(OPENAI_API_URL).log_err();
fn save_credentials(
&self,
cx: &mut AppContext,
credential: ProviderCredential,
) -> BoxFuture<()> {
*self.credential.write() = credential.clone();
let credential = credential.clone();
let write_credentials = match credential {
ProviderCredential::Credentials { api_key } => {
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
}
_ => None,
};
async move {
if let Some(write_credentials) = write_credentials {
write_credentials.await.log_err();
}
}
.boxed()
}
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
*self.credential.write() = ProviderCredential::NoCredentials;
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
async move {
delete_credentials.await.log_err();
}
.boxed()
}
}

View file

@ -104,11 +104,22 @@ impl CredentialProvider for FakeEmbeddingProvider {
fn has_credentials(&self) -> bool {
true
}
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
ProviderCredential::NotNeeded
fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
async { ProviderCredential::NotNeeded }.boxed()
}
fn save_credentials(
&self,
_cx: &mut AppContext,
_credential: ProviderCredential,
) -> BoxFuture<()> {
async {}.boxed()
}
fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> {
async {}.boxed()
}
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
fn delete_credentials(&self, _cx: &mut AppContext) {}
}
#[async_trait]
@ -165,11 +176,22 @@ impl CredentialProvider for FakeCompletionProvider {
fn has_credentials(&self) -> bool {
true
}
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
ProviderCredential::NotNeeded
fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
async { ProviderCredential::NotNeeded }.boxed()
}
fn save_credentials(
&self,
_cx: &mut AppContext,
_credential: ProviderCredential,
) -> BoxFuture<()> {
async {}.boxed()
}
fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> {
async {}.boxed()
}
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
fn delete_credentials(&self, _cx: &mut AppContext) {}
}
impl CompletionProvider for FakeCompletionProvider {

View file

@ -6,14 +6,12 @@ use crate::{
NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
};
use ai::prompts::repository_context::PromptCodeSnippet;
use ai::{
auth::ProviderCredential,
completion::{CompletionProvider, CompletionRequest},
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
};
use ai::prompts::repository_context::PromptCodeSnippet;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
use client::telemetry::AssistantKind;
@ -220,23 +218,9 @@ impl AssistantPanel {
_: &InlineAssist,
cx: &mut ViewContext<Workspace>,
) {
let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
if this.update(cx, |assistant, cx| {
if !assistant.has_credentials() {
assistant.load_credentials(cx);
};
assistant.has_credentials()
}) {
this
} else {
workspace.focus_panel::<AssistantPanel>(cx);
return;
}
} else {
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
let active_editor = if let Some(active_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
@ -245,12 +229,32 @@ impl AssistantPanel {
} else {
return;
};
let project = workspace.project().clone();
let project = workspace.project();
if assistant.update(cx, |assistant, _| assistant.has_credentials()) {
assistant.update(cx, |assistant, cx| {
assistant.new_inline_assist(&active_editor, cx, &project)
});
} else {
let assistant = assistant.downgrade();
cx.spawn(|workspace, mut cx| async move {
assistant
.update(&mut cx, |assistant, cx| assistant.load_credentials(cx))?
.await;
if assistant.update(&mut cx, |assistant, _| assistant.has_credentials())? {
assistant.update(&mut cx, |assistant, cx| {
assistant.new_inline_assist(&active_editor, cx, &project)
})?;
} else {
workspace.update(&mut cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx)
})?;
}
this.update(cx, |assistant, cx| {
assistant.new_inline_assist(&active_editor, cx, project)
});
anyhow::Ok(())
})
.detach_and_log_err(cx)
}
}
fn new_inline_assist(
@ -290,9 +294,6 @@ impl AssistantPanel {
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
let provider = self.completion_provider.clone();
// Retrieve Credentials Authenticates the Provider
provider.retrieve_credentials(cx);
let codegen = cx.new_model(|cx| {
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
});
@ -845,11 +846,18 @@ impl AssistantPanel {
api_key: api_key.clone(),
};
self.completion_provider.save_credentials(cx, credential);
let completion_provider = self.completion_provider.clone();
cx.spawn(|this, mut cx| async move {
cx.update(|cx| completion_provider.save_credentials(cx, credential))?
.await;
self.api_key_editor.take();
self.focus_handle.focus(cx);
cx.notify();
this.update(&mut cx, |this, cx| {
this.api_key_editor.take();
this.focus_handle.focus(cx);
cx.notify();
})
})
.detach_and_log_err(cx);
}
} else {
cx.propagate();
@ -857,10 +865,17 @@ impl AssistantPanel {
}
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
self.completion_provider.delete_credentials(cx);
self.api_key_editor = Some(build_api_key_editor(cx));
self.focus_handle.focus(cx);
cx.notify();
let completion_provider = self.completion_provider.clone();
cx.spawn(|this, mut cx| async move {
cx.update(|cx| completion_provider.delete_credentials(cx))?
.await;
this.update(&mut cx, |this, cx| {
this.api_key_editor = Some(build_api_key_editor(cx));
this.focus_handle.focus(cx);
cx.notify();
})
})
.detach_and_log_err(cx);
}
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
@ -1107,8 +1122,16 @@ impl AssistantPanel {
self.completion_provider.has_credentials()
}
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
self.completion_provider.retrieve_credentials(cx);
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
let completion_provider = self.completion_provider.clone();
cx.spawn(|_, mut cx| async move {
if let Some(retrieve_credentials) = cx
.update(|cx| completion_provider.retrieve_credentials(cx))
.log_err()
{
retrieve_credentials.await;
}
})
}
}
@ -1314,11 +1337,16 @@ impl Panel for AssistantPanel {
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
if active {
self.load_credentials(cx);
if self.editors.is_empty() {
self.new_conversation(cx);
}
let load_credentials = self.load_credentials(cx);
cx.spawn(|this, mut cx| async move {
load_credentials.await;
this.update(&mut cx, |this, cx| {
if this.editors.is_empty() {
this.new_conversation(cx);
}
})
})
.detach_and_log_err(cx);
}
}

View file

@ -700,8 +700,8 @@ impl Client {
}
}
pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
read_credentials_from_keychain(cx).is_some()
pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
read_credentials_from_keychain(cx).await.is_some()
}
#[async_recursion(?Send)]
@ -732,7 +732,7 @@ impl Client {
let mut read_from_keychain = false;
let mut credentials = self.state.read().credentials.clone();
if credentials.is_none() && try_keychain {
credentials = read_credentials_from_keychain(cx);
credentials = read_credentials_from_keychain(cx).await;
read_from_keychain = credentials.is_some();
}
if credentials.is_none() {
@ -770,7 +770,7 @@ impl Client {
Ok(conn) => {
self.state.write().credentials = Some(credentials.clone());
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
write_credentials_to_keychain(credentials, cx).log_err();
write_credentials_to_keychain(credentials, cx).await.log_err();
}
futures::select_biased! {
@ -784,7 +784,7 @@ impl Client {
Err(EstablishConnectionError::Unauthorized) => {
self.state.write().credentials.take();
if read_from_keychain {
delete_credentials_from_keychain(cx).log_err();
delete_credentials_from_keychain(cx).await.log_err();
self.set_status(Status::SignedOut, cx);
self.authenticate_and_connect(false, cx).await
} else {
@ -1350,14 +1350,16 @@ impl Client {
}
}
fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
if IMPERSONATE_LOGIN.is_some() {
return None;
}
let (user_id, access_token) = cx
.update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
.ok()??;
.update(|cx| cx.read_credentials(&ZED_SERVER_URL))
.log_err()?
.await
.log_err()??;
Some(Credentials {
user_id: user_id.parse().ok()?,
@ -1365,7 +1367,10 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
})
}
fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> {
async fn write_credentials_to_keychain(
credentials: Credentials,
cx: &AsyncAppContext,
) -> Result<()> {
cx.update(move |cx| {
cx.write_credentials(
&ZED_SERVER_URL,
@ -1373,10 +1378,12 @@ fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext)
credentials.access_token.as_bytes(),
)
})?
.await
}
fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
.await
}
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";

View file

@ -523,17 +523,22 @@ impl AppContext {
}
/// Writes credentials to the platform keychain.
pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
pub fn write_credentials(
&self,
url: &str,
username: &str,
password: &[u8],
) -> Task<Result<()>> {
self.platform.write_credentials(url, username, password)
}
/// Reads credentials from the platform keychain.
pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
pub fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
self.platform.read_credentials(url)
}
/// Deletes credentials from the platform keychain.
pub fn delete_credentials(&self, url: &str) -> Result<()> {
pub fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
self.platform.delete_credentials(url)
}

View file

@ -9,7 +9,7 @@ use crate::{
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
Scene, SharedString, Size, TaskLabel, WindowContext,
Scene, SharedString, Size, Task, TaskLabel, WindowContext,
};
use anyhow::anyhow;
use async_task::Runnable;
@ -108,9 +108,9 @@ pub(crate) trait Platform: 'static {
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
fn delete_credentials(&self, url: &str) -> Result<()>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
}
/// A handle to a platform's display, e.g. a monitor or laptop screen.

View file

@ -3,7 +3,7 @@ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
};
use anyhow::anyhow;
use block::ConcreteBlock;
@ -856,104 +856,115 @@ impl Platform for MacPlatform {
}
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
let url = CFString::from(url);
let username = CFString::from(username);
let password = CFData::from_buffer(password);
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
let url = url.to_string();
let username = username.to_string();
let password = password.to_vec();
self.background_executor().spawn(async move {
unsafe {
use security::*;
unsafe {
use security::*;
let url = CFString::from(url.as_str());
let username = CFString::from(username.as_str());
let password = CFData::from_buffer(&password);
// First, check if there are already credentials for the given server. If so, then
// update the username and password.
let mut verb = "updating";
let mut query_attrs = CFMutableDictionary::with_capacity(2);
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
// First, check if there are already credentials for the given server. If so, then
// update the username and password.
let mut verb = "updating";
let mut query_attrs = CFMutableDictionary::with_capacity(2);
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
let mut attrs = CFMutableDictionary::with_capacity(4);
attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
let mut attrs = CFMutableDictionary::with_capacity(4);
attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
let mut status = SecItemUpdate(
query_attrs.as_concrete_TypeRef(),
attrs.as_concrete_TypeRef(),
);
let mut status = SecItemUpdate(
query_attrs.as_concrete_TypeRef(),
attrs.as_concrete_TypeRef(),
);
// If there were no existing credentials for the given server, then create them.
if status == errSecItemNotFound {
verb = "creating";
status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
// If there were no existing credentials for the given server, then create them.
if status == errSecItemNotFound {
verb = "creating";
status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
}
if status != errSecSuccess {
return Err(anyhow!("{} password failed: {}", verb, status));
}
}
if status != errSecSuccess {
return Err(anyhow!("{} password failed: {}", verb, status));
}
}
Ok(())
Ok(())
})
}
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
let url = CFString::from(url);
let cf_true = CFBoolean::true_value().as_CFTypeRef();
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
let url = url.to_string();
self.background_executor().spawn(async move {
let url = CFString::from(url.as_str());
let cf_true = CFBoolean::true_value().as_CFTypeRef();
unsafe {
use security::*;
unsafe {
use security::*;
// Find any credentials for the given server URL.
let mut attrs = CFMutableDictionary::with_capacity(5);
attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
attrs.set(kSecReturnAttributes as *const _, cf_true);
attrs.set(kSecReturnData as *const _, cf_true);
// Find any credentials for the given server URL.
let mut attrs = CFMutableDictionary::with_capacity(5);
attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
attrs.set(kSecReturnAttributes as *const _, cf_true);
attrs.set(kSecReturnData as *const _, cf_true);
let mut result = CFTypeRef::from(ptr::null());
let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
match status {
security::errSecSuccess => {}
security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
_ => return Err(anyhow!("reading password failed: {}", status)),
let mut result = CFTypeRef::from(ptr::null());
let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
match status {
security::errSecSuccess => {}
security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
_ => return Err(anyhow!("reading password failed: {}", status)),
}
let result = CFType::wrap_under_create_rule(result)
.downcast::<CFDictionary>()
.ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
let username = result
.find(kSecAttrAccount as *const _)
.ok_or_else(|| anyhow!("account was missing from keychain item"))?;
let username = CFType::wrap_under_get_rule(*username)
.downcast::<CFString>()
.ok_or_else(|| anyhow!("account was not a string"))?;
let password = result
.find(kSecValueData as *const _)
.ok_or_else(|| anyhow!("password was missing from keychain item"))?;
let password = CFType::wrap_under_get_rule(*password)
.downcast::<CFData>()
.ok_or_else(|| anyhow!("password was not a string"))?;
Ok(Some((username.to_string(), password.bytes().to_vec())))
}
let result = CFType::wrap_under_create_rule(result)
.downcast::<CFDictionary>()
.ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
let username = result
.find(kSecAttrAccount as *const _)
.ok_or_else(|| anyhow!("account was missing from keychain item"))?;
let username = CFType::wrap_under_get_rule(*username)
.downcast::<CFString>()
.ok_or_else(|| anyhow!("account was not a string"))?;
let password = result
.find(kSecValueData as *const _)
.ok_or_else(|| anyhow!("password was missing from keychain item"))?;
let password = CFType::wrap_under_get_rule(*password)
.downcast::<CFData>()
.ok_or_else(|| anyhow!("password was not a string"))?;
Ok(Some((username.to_string(), password.bytes().to_vec())))
}
})
}
fn delete_credentials(&self, url: &str) -> Result<()> {
let url = CFString::from(url);
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
let url = url.to_string();
unsafe {
use security::*;
self.background_executor().spawn(async move {
unsafe {
use security::*;
let mut query_attrs = CFMutableDictionary::with_capacity(2);
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
let url = CFString::from(url.as_str());
let mut query_attrs = CFMutableDictionary::with_capacity(2);
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
if status != errSecSuccess {
return Err(anyhow!("delete password failed: {}", status));
if status != errSecSuccess {
return Err(anyhow!("delete password failed: {}", status));
}
}
}
Ok(())
Ok(())
})
}
}

View file

@ -1,6 +1,7 @@
use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow,
WindowOptions,
};
use anyhow::{anyhow, Result};
use collections::VecDeque;
@ -280,16 +281,16 @@ impl Platform for TestPlatform {
self.current_clipboard_item.lock().clone()
}
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> {
Ok(())
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
Ok(None)
fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
Task::ready(Ok(None))
}
fn delete_credentials(&self, _url: &str) -> Result<()> {
Ok(())
fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn double_click_interval(&self) -> std::time::Duration {

View file

@ -278,14 +278,22 @@ impl SemanticIndex {
.map(|semantic_index| semantic_index.clone())
}
pub fn authenticate(&mut self, cx: &mut AppContext) -> bool {
pub fn authenticate(&mut self, cx: &mut AppContext) -> Task<bool> {
if !self.embedding_provider.has_credentials() {
self.embedding_provider.retrieve_credentials(cx);
} else {
return true;
}
let embedding_provider = self.embedding_provider.clone();
cx.spawn(|cx| async move {
if let Some(retrieve_credentials) = cx
.update(|cx| embedding_provider.retrieve_credentials(cx))
.log_err()
{
retrieve_credentials.await;
}
self.embedding_provider.has_credentials()
embedding_provider.has_credentials()
})
} else {
Task::ready(true)
}
}
pub fn is_authenticated(&self) -> bool {
@ -1005,12 +1013,26 @@ impl SemanticIndex {
project: Model<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.is_authenticated() {
if !self.authenticate(cx) {
return Task::ready(Err(anyhow!("user is not authenticated")));
}
if self.is_authenticated() {
self.index_project_internal(project, cx)
} else {
let authenticate = self.authenticate(cx);
cx.spawn(|this, mut cx| async move {
if authenticate.await {
this.update(&mut cx, |this, cx| this.index_project_internal(project, cx))?
.await
} else {
Err(anyhow!("user is not authenticated"))
}
})
}
}
fn index_project_internal(
&mut self,
project: Model<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.projects.contains_key(&project.downgrade()) {
let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {

View file

@ -376,7 +376,7 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
if client::IMPERSONATE_LOGIN.is_some() {
client.authenticate_and_connect(false, &cx).await?;
}
} else if client.has_keychain_credentials(&cx) {
} else if client.has_keychain_credentials(&cx).await {
client.authenticate_and_connect(true, &cx).await?;
}
Ok::<_, anyhow::Error>(())