diff --git a/src/httpclient.rs b/src/httpclient.rs index a0be177..35f0139 100644 --- a/src/httpclient.rs +++ b/src/httpclient.rs @@ -34,7 +34,7 @@ use crate::data::renku_url::RenkuUrl; use self::data::*; use auth::{Response, UserCode}; -use keystore::{KeyringStore, Keystore}; +use keystore::{AsyncKeystore, KeyringStore}; use openidconnect::OAuth2TokenResponse; use regex::Regex; use reqwest::{Certificate, ClientBuilder, IntoUrl, RequestBuilder, Url}; @@ -156,14 +156,10 @@ impl Client { let keystore = keystore::KeyringStore::new(renku_url.clone()).context(KeystoreSnafu)?; - let auth_data = access_token.or(keystore - .read_token() - .context(KeystoreSnafu)? - .map(|r| r.response.access_token().secret().clone())); let client = client_builder.build().context(ClientCreateSnafu)?; Ok(Client { client, - access_token: auth_data, + access_token, settings: Settings { proxy, trusted_certificate, @@ -186,10 +182,22 @@ impl Client { .context(UrlParseSnafu) } - fn set_bearer_token(&self, b: RequestBuilder) -> RequestBuilder { + async fn get_access_token(&self) -> Result, Error> { match &self.access_token { - Some(token) => b.bearer_auth(token), - None => b, + Some(t) => Ok(Some(t.to_string())), + None => Ok(self + .keystore + .read_token_async() + .await + .context(KeystoreSnafu)? + .map(|r| r.response.access_token().secret().clone())), + } + } + + async fn set_bearer_token(&self, b: RequestBuilder) -> Result { + match self.get_access_token().await? { + Some(token) => Ok(b.bearer_auth(token)), + None => Ok(b), } } @@ -224,7 +232,7 @@ impl Client { async fn json_get(&self, path: &str) -> Result { let url = self.make_url(path)?; log::debug!("JSON GET: {}", url); - let req = self.set_bearer_token(self.client.get(url.clone())); + let req = self.set_bearer_token(self.client.get(url.clone())).await?; self.run_request(req, url).await } @@ -237,6 +245,7 @@ impl Client { let url = self.make_url(path)?; let req = self .set_bearer_token(self.client.post(url.clone())) + .await? .json::(body); self.run_request(req, url).await } @@ -247,7 +256,7 @@ impl Client { /// expected structure. async fn json_get_option(&self, path: &str) -> Result, Error> { let url = self.make_url(path)?; - let req = self.set_bearer_token(self.client.get(url.clone())); + let req = self.set_bearer_token(self.client.get(url.clone())).await?; let result = self.run_request(req, url).await; match result { @@ -392,6 +401,7 @@ impl Client { let path = format!("/api/data/sessions/{}", session_id); let url = self.make_url(&path)?; self.set_bearer_token(self.client.delete(url.clone())) + .await? .send() .await .context(HttpSnafu { url })?; @@ -405,7 +415,7 @@ impl Client { url, mode.as_ref().map_or("", |e| e.to_query_param()) ); - let mut req = self.set_bearer_token(self.client.get(url.clone())); + let mut req = self.set_bearer_token(self.client.get(url.clone())).await?; if let Some(m) = mode { req = req.query(&[("session_type", m.to_query_param())]) } @@ -428,7 +438,10 @@ impl Client { pub async fn complete_login_flow(&self, code: UserCode) -> Result { let r = auth::poll_tokens(code).await?; - self.keystore.write_token(&r).context(KeystoreSnafu)?; + self.keystore + .write_token_async(&r) + .await + .context(KeystoreSnafu)?; Ok(r) } @@ -439,6 +452,6 @@ impl Client { } pub async fn clear_token(&self) -> Result<(), Error> { - self.keystore.clear().context(KeystoreSnafu) + self.keystore.clear_async().await.context(KeystoreSnafu) } } diff --git a/src/httpclient/keystore.rs b/src/httpclient/keystore.rs index cf35337..b4b3676 100644 --- a/src/httpclient/keystore.rs +++ b/src/httpclient/keystore.rs @@ -1,13 +1,13 @@ use std::{path::PathBuf, sync::Arc}; +use crate::data::renku_url::RenkuUrl; use db_keystore::{DbKeyStore, DbKeyStoreConfig}; use directories::ProjectDirs; use keyring_core::{self, CredentialStore}; use log; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; - -use crate::data::renku_url::RenkuUrl; +use tokio::task; use super::auth::Response; @@ -35,17 +35,6 @@ pub enum Error { FromJson { source: serde_json::Error }, } -/// Sets the global default keyring store for the underlying keyring library. -pub fn set_default_global_keyring_store() -> Result { - if keyring_core::get_default_store().is_some() { - log::debug!("A keystore has already been configured"); - return Ok(false); - } - let ks = KeyringStore::create_underlying_keyring()?; - keyring_core::set_default_store(ks); - Ok(true) -} - /// Keystore api used with the renku http client. pub trait Keystore { fn write_token(&self, token: &Response) -> Result<(), Error>; @@ -53,6 +42,12 @@ pub trait Keystore { fn clear(&self) -> Result<(), Error>; } +pub trait AsyncKeystore { + fn write_token_async(&self, token: &Response) -> impl Future>; + fn read_token_async(&self) -> impl Future, Error>>; + fn clear_async(&self) -> impl Future>; +} + pub struct KeyringStore { renku_url: RenkuUrl, store: Arc, @@ -145,7 +140,20 @@ impl KeyringStore { fn build_entry(&self) -> Result { let service = self.renku_url.as_url().domain().unwrap_or("renku"); - let user = whoami::account().unwrap_or_else(|_| "default-user".to_string()); + let user = whoami::username().unwrap_or_else(|_| "default-user".to_string()); + self.store + .as_ref() + .build(service, &user, None) + .context(BuildEntrySnafu) + } + + async fn build_entry_async(&self) -> Result { + let service = self.renku_url.as_url().domain().unwrap_or("renku"); + let user = task::spawn_blocking(|| { + whoami::username().unwrap_or_else(|_| "default-user".to_string()) + }) + .await + .unwrap(); self.store .as_ref() .build(service, &user, None) @@ -183,6 +191,41 @@ impl Keystore for KeyringStore { } } +impl AsyncKeystore for KeyringStore { + async fn write_token_async(&self, token: &Response) -> Result<(), Error> { + let entry = self.build_entry_async().await?; + let cnt = serde_json::to_vec(token).context(ToJsonSnafu)?; + task::spawn_blocking(move || entry.set_secret(&cnt).context(WriteSecretSnafu)) + .await + .unwrap() + } + + async fn read_token_async(&self) -> Result, Error> { + let entry = self.build_entry_async().await?; + task::spawn_blocking(move || match entry.get_secret() { + Ok(secret) => { + let resp = serde_json::from_slice::(&secret).context(FromJsonSnafu)?; + Ok(Some(resp)) + } + Err(keyring_core::Error::NoEntry) => Ok(None), + Err(err) => Err(Error::ReadSecret { source: err }), + }) + .await + .unwrap() + } + + async fn clear_async(&self) -> Result<(), Error> { + let entry = self.build_entry_async().await?; + task::spawn_blocking(move || match entry.delete_credential() { + Ok(()) => Ok(()), + Err(keyring_core::Error::NoEntry) => Ok(()), + Err(err) => Err(Error::WriteSecret { source: err }), + }) + .await + .unwrap() + } +} + #[cfg(target_os = "windows")] fn get_native_keystore() -> Result>, Error> { use std::sync::Arc;