From c6a8fe2545e854d3f2480d6824ac8f9a724d7eed Mon Sep 17 00:00:00 2001 From: Alan Chen Date: Mon, 9 Dec 2024 15:47:19 -0800 Subject: [PATCH] fix: multi-org token remote (#4291) * fix: multi-org token remote * fixup: removue unused is_v4 --- Cargo.lock | 2 +- .../fluvio-hub-protocol/src/infinyon_tok.rs | 101 ++++++++++-------- crates/fluvio-hub-util/src/hubaccess.rs | 34 +++--- 3 files changed, 69 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a47fb476b1..15fe77f191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2755,7 +2755,7 @@ dependencies = [ [[package]] name = "fluvio-extension-common" -version = "0.14.3" +version = "0.14.4" dependencies = [ "anyhow", "async-trait", diff --git a/crates/fluvio-hub-protocol/src/infinyon_tok.rs b/crates/fluvio-hub-protocol/src/infinyon_tok.rs index 7edd7cfdd2..c5fc095fde 100644 --- a/crates/fluvio-hub-protocol/src/infinyon_tok.rs +++ b/crates/fluvio-hub-protocol/src/infinyon_tok.rs @@ -3,24 +3,21 @@ // 'read_infinyon_token' function to read from the current login config // use std::collections::HashMap; -use std::env; use std::fs; use std::path::Path; use serde::{Deserialize, Serialize}; use serde_json; -use tracing::debug; use fluvio_types::defaults::CLI_CONFIG_PATH; -const INFINYON_CONFIG_PATH_ENV: &str = "INFINYON_CONFIG_PATH"; const DEFAULT_LOGINS_DIR: &str = "logins"; // from logins.rs const CURRENT_LOGIN_FILE_NAME: &str = "current"; type InfinyonToken = String; type InfinyonRemote = String; -#[derive(thiserror::Error, Debug)] +#[derive(Clone, thiserror::Error, Debug)] pub enum InfinyonCredentialError { #[error("no org access token found, please login or switch to an org with 'fluvio cloud org switch'")] MissingOrgToken, @@ -32,26 +29,30 @@ pub enum InfinyonCredentialError { UnableToParseCredentials, } +#[derive(Clone)] pub enum AccessToken { - V3(InfinyonToken), + V3((InfinyonToken, InfinyonRemote)), V4(CliAccessTokens), } impl AccessToken { - pub fn get_hub_token(&self) -> Result { + pub fn get_token(&self) -> Result { match self { - AccessToken::V3(tok) => Ok(tok.to_owned()), - AccessToken::V4(cli_access_tokens) => cli_access_tokens.get_current_org_token(), + AccessToken::V3((token, _remote)) => Ok(token.clone()), + AccessToken::V4(token) => Ok(token.get_current_org_token()?), } } - pub fn is_v4(&self) -> bool { - matches!(self, AccessToken::V4(_)) + pub fn get_remote(&self) -> Result { + match self { + AccessToken::V3((_token, remote)) => Ok(remote.clone()), + AccessToken::V4(cli_access_tokens) => Ok(cli_access_tokens.remote.clone()), + } } } // multi-org access token output -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct CliAccessTokens { pub remote: String, pub user_access_token: Option, @@ -80,26 +81,40 @@ impl CliAccessTokens { /// replaces old read_infinyon_token pub fn read_access_token() -> Result { - if let Ok(cli_access_tokens) = read_infinyon_token_v4() { - println!( - "Using org access: {}", - cli_access_tokens.get_current_org_name()? - ); - return Ok(AccessToken::V4(cli_access_tokens)); - } - let tok = read_infinyon_token_v3()?; - Ok(AccessToken::V3(tok)) + // read token into cache once + static TOKEN_CACHE: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + TOKEN_CACHE + .get_or_init(|| { + let token = read_access_token_impl(); + match token { + Ok(AccessToken::V3(_)) => { + tracing::debug!("using v3 token"); + } + Ok(AccessToken::V4(ref cli_access_token)) => { + tracing::debug!("using v4 token"); + println!( + "Using org access: {}", + cli_access_token.get_current_org_name()? + ); + } + Err(ref err) => { + tracing::debug!("failed to read token: {}", err); + } + } + token + }) + .clone() } -pub fn read_infinyon_token() -> Result { +// replaces old read_infinyon_token +fn read_access_token_impl() -> Result { if let Ok(cli_access_tokens) = read_infinyon_token_v4() { - tracing::debug!( - "using v4 token for org {}", - cli_access_tokens.get_current_org_name()? - ); - return cli_access_tokens.get_current_org_token(); + return Ok(AccessToken::V4(cli_access_tokens)); } - read_infinyon_token_v3() + let pair = read_infinyon_token_v3()?; + Ok(AccessToken::V3(pair)) } pub fn read_infinyon_token_v4() -> Result { @@ -137,31 +152,27 @@ fn read_infinyon_token_v4_cli(cloud_bin: &str) -> Result Result { - let cfgpath = default_file_path(); - // this will read the indirection file to resolve the profile - let cred = Credentials::try_load(cfgpath)?; - Ok(cred.token) -} - -pub fn read_infinyon_token_rem() -> Result<(InfinyonToken, InfinyonRemote), InfinyonCredentialError> +// deprecated, will be removed after multi-org is stable +pub fn read_infinyon_token_v3() -> Result<(InfinyonToken, InfinyonRemote), InfinyonCredentialError> { - // the ENV variable should point directly to the applicable profile - if let Ok(profilepath) = env::var(INFINYON_CONFIG_PATH_ENV) { - let cred = Credentials::load(Path::new(&profilepath))?; - debug!( - path = profilepath, - "profile loaded from INFINYON_CONFIG_PATH_ENV" - ); - return Ok((cred.token, cred.remote)); - } let cfgpath = default_file_path(); // this will read the indirection file to resolve the profile let cred = Credentials::try_load(cfgpath)?; Ok((cred.token, cred.remote)) } +// read remote (older api) +pub fn read_infinyon_token_rem() -> Result { + let tok = read_access_token()?; + tok.get_remote() +} + +// read token (older api) +pub fn read_infinyon_token() -> Result { + let access = read_access_token()?; + access.get_token() +} + #[derive(Debug, PartialEq, Deserialize, Serialize)] struct Credentials { remote: String, diff --git a/crates/fluvio-hub-util/src/hubaccess.rs b/crates/fluvio-hub-util/src/hubaccess.rs index ec3dcca436..a99cb9e103 100644 --- a/crates/fluvio-hub-util/src/hubaccess.rs +++ b/crates/fluvio-hub-util/src/hubaccess.rs @@ -145,15 +145,6 @@ impl HubAccess { self.get_action_auth(ACTION_PUBLISH).await } - pub async fn get_action_auth_with_token( - &self, - action: &str, - authn_token: &str, - ) -> Result { - let access_token = AccessToken::V3(authn_token.to_string()); - self.make_action_token(action, Some(access_token)).await - } - async fn get_action_auth(&self, action: &str) -> Result { let access_token = read_access_token().ok(); self.make_action_token(action, access_token).await @@ -170,18 +161,17 @@ impl HubAccess { let mut builder = http::Request::post(&api_url); match token { - Some(AccessToken::V4(cli_access_tokens)) => { - let org = cli_access_tokens.get_current_org_name()?; - let tok = cli_access_tokens - .org_access_tokens - .get(&org) - .ok_or(HubError::HubAccess("Missing org token".to_string()))?; - let authn_token = format!("Bearer {tok}"); - builder = builder.header("Authorization", &authn_token); - } - Some(AccessToken::V3(tok)) => { - // v3 does not use "Bearer" prefix - builder = builder.header("Authorization", &tok); + Some(token) => { + let auth_token = token.get_token()?; + match token { + AccessToken::V4(_) => { + builder = builder.header("Authorization", format!("Bearer {auth_token}")); + } + AccessToken::V3(_) => { + // v3 does not use "Bearer" prefix + builder = builder.header("Authorization", auth_token); + } + } } None => { // no token is allowed for some actions like downloading public @@ -357,7 +347,7 @@ struct ReplyHubref { #[cfg(not(target_arch = "wasm32"))] fn get_hubref() -> Option { - let Ok((_, fcremote)) = read_infinyon_token_rem() else { + let Ok(fcremote) = read_infinyon_token_rem() else { return None; }; if fcremote == DEFAULT_CLOUD_REMOTE {