Skip to content
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
41 changes: 27 additions & 14 deletions src/httpclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -186,10 +182,22 @@ impl Client {
.context(UrlParseSnafu)
}

fn set_bearer_token(&self, b: RequestBuilder) -> RequestBuilder {
async fn get_access_token(&self) -> Result<Option<String>, 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<RequestBuilder, Error> {
match self.get_access_token().await? {
Some(token) => Ok(b.bearer_auth(token)),
None => Ok(b),
}
}

Expand Down Expand Up @@ -224,7 +232,7 @@ impl Client {
async fn json_get<R: DeserializeOwned>(&self, path: &str) -> Result<R, Error> {
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
}

Expand All @@ -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::<I>(body);
self.run_request(req, url).await
}
Expand All @@ -247,7 +256,7 @@ impl Client {
/// expected structure.
async fn json_get_option<R: DeserializeOwned>(&self, path: &str) -> Result<Option<R>, 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 {
Expand Down Expand Up @@ -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 })?;
Expand All @@ -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())])
}
Expand All @@ -428,7 +438,10 @@ impl Client {

pub async fn complete_login_flow(&self, code: UserCode) -> Result<Response, Error> {
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)
}

Expand All @@ -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)
}
}
71 changes: 57 additions & 14 deletions src/httpclient/keystore.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -35,24 +35,19 @@ 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<bool, Error> {
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>;
fn read_token(&self) -> Result<Option<Response>, Error>;
fn clear(&self) -> Result<(), Error>;
}

pub trait AsyncKeystore {
fn write_token_async(&self, token: &Response) -> impl Future<Output = Result<(), Error>>;
fn read_token_async(&self) -> impl Future<Output = Result<Option<Response>, Error>>;
fn clear_async(&self) -> impl Future<Output = Result<(), Error>>;
}

pub struct KeyringStore {
renku_url: RenkuUrl,
store: Arc<keyring_core::CredentialStore>,
Expand Down Expand Up @@ -145,7 +140,20 @@ impl KeyringStore {

fn build_entry(&self) -> Result<keyring_core::Entry, Error> {
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<keyring_core::Entry, Error> {
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)
Expand Down Expand Up @@ -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<Option<Response>, Error> {
let entry = self.build_entry_async().await?;
task::spawn_blocking(move || match entry.get_secret() {
Ok(secret) => {
let resp = serde_json::from_slice::<Response>(&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<Option<Arc<keyring_core::CredentialStore>>, Error> {
use std::sync::Arc;
Expand Down
Loading