use super::dlf_signer::{DLFDefaultSigner, DLFOpenApiSigner};
use crate::api::auth::dlf_provider::DLFTokenLoaderFactory;
use crate::api::auth::{BearerTokenAuthProvider, DLFAuthProvider, DLFToken};
use crate::api::AuthProvider;
use crate::common::{CatalogOptions, Options};
use crate::Error;
use regex::Regex;
pub struct DLFAuthProviderFactory;
impl DLFAuthProviderFactory {
const REGION_PATTERN: &'static str = r"(?:pre-)?([a-z]+-[a-z]+(?:-\d+)?)";
pub fn parse_region_from_uri(uri: Option<&str>) -> Option<String> {
let uri = uri?;
let re = Regex::new(Self::REGION_PATTERN).ok()?;
let caps = re.captures(uri)?;
caps.get(1).map(|m| m.as_str().to_string())
}
pub fn parse_signing_algo_from_uri(uri: Option<&str>) -> &'static str {
if let Some(uri) = uri {
let host = uri.to_lowercase();
let host = host
.strip_prefix("http://")
.unwrap_or(host.strip_prefix("https://").unwrap_or(&host));
let host = host.split('/').next().unwrap_or("");
let host = host.split(':').next().unwrap_or("");
if host.starts_with("dlfnext") {
return DLFOpenApiSigner::IDENTIFIER;
}
}
DLFDefaultSigner::IDENTIFIER
}
pub fn create_provider(options: &Options) -> Result<Box<dyn AuthProvider>, Error> {
let uri = options
.get(CatalogOptions::URI)
.ok_or_else(|| Error::ConfigInvalid {
message: "URI is required for DLF authentication".to_string(),
})?
.clone();
let region = options
.get(CatalogOptions::DLF_REGION)
.cloned()
.or_else(|| Self::parse_region_from_uri(Some(&uri)))
.ok_or_else(|| Error::ConfigInvalid {
message: "Could not get region from config or URI. Please set 'dlf.region' or use a standard DLF endpoint URI.".to_string(),
})?;
let signing_algorithm = options
.get(CatalogOptions::DLF_SIGNING_ALGORITHM)
.map(|s| s.as_str())
.filter(|s| *s != DLFDefaultSigner::IDENTIFIER)
.unwrap_or_else(|| Self::parse_signing_algo_from_uri(Some(&uri)))
.to_string();
let dlf_provider = DLFAuthProvider::new(
uri,
region,
signing_algorithm,
DLFToken::from_options(options),
DLFTokenLoaderFactory::create_token_loader(options),
)?;
Ok(Box::new(dlf_provider))
}
}
pub struct AuthProviderFactory;
impl AuthProviderFactory {
pub fn create_auth_provider(options: &Options) -> Result<Box<dyn AuthProvider>, Error> {
let provider = options.get(CatalogOptions::TOKEN_PROVIDER);
match provider.map(|s| s.as_str()) {
Some("bear") => {
let token =
options
.get(CatalogOptions::TOKEN)
.ok_or_else(|| Error::ConfigInvalid {
message: "token is required for bearer authentication".to_string(),
})?;
Ok(Box::new(BearerTokenAuthProvider::new(token)))
}
Some("dlf") => DLFAuthProviderFactory::create_provider(options),
Some(unknown) => Err(Error::ConfigInvalid {
message: format!("Unknown auth provider: {unknown}"),
}),
None => Err(Error::ConfigInvalid {
message: "auth provider is required".to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use crate::api::auth::base::AUTHORIZATION_HEADER_KEY;
use super::super::RESTAuthParameter;
use super::*;
use std::collections::HashMap;
#[tokio::test]
async fn test_create_bearer_provider() {
let mut options = Options::new();
options.set(CatalogOptions::TOKEN_PROVIDER, "bear");
options.set(CatalogOptions::TOKEN, "test-token");
let provider = AuthProviderFactory::create_auth_provider(&options).unwrap();
let base_header = HashMap::new();
let param = RESTAuthParameter::new("GET", "/test", None, HashMap::new());
let result = provider
.merge_auth_header(base_header, ¶m)
.await
.unwrap();
assert_eq!(
result.get("Authorization"),
Some(&"Bearer test-token".to_string())
);
}
#[test]
fn test_none_provider_error() {
let options = Options::new();
let result = AuthProviderFactory::create_auth_provider(&options);
assert!(result.is_err());
}
#[test]
fn test_unknown_provider() {
let mut options = Options::new();
options.set(CatalogOptions::TOKEN_PROVIDER, "unknown");
let result = AuthProviderFactory::create_auth_provider(&options);
assert!(result.is_err());
}
#[tokio::test]
async fn test_create_dlf_provider() {
let mut options = Options::new();
options.set(CatalogOptions::TOKEN_PROVIDER, "dlf");
options.set(CatalogOptions::URI, "http://dlf-asdaswfnb.net/");
options.set(CatalogOptions::DLF_REGION, "cn-hangzhou");
options.set(CatalogOptions::DLF_ACCESS_KEY_ID, "test_key_id");
options.set(CatalogOptions::DLF_ACCESS_KEY_SECRET, "test_key_secret");
let provider = AuthProviderFactory::create_auth_provider(&options).unwrap();
let base_header = HashMap::new();
let param = RESTAuthParameter::new("GET", "/test", None, HashMap::new());
let result = provider
.merge_auth_header(base_header, ¶m)
.await
.unwrap();
assert!(result.contains_key(AUTHORIZATION_HEADER_KEY));
}
#[test]
fn test_dlf_provider_missing_region() {
let mut options = Options::new();
options.set(CatalogOptions::TOKEN_PROVIDER, "dlf");
options.set(CatalogOptions::URI, "http://example.com/");
options.set(CatalogOptions::DLF_ACCESS_KEY_ID, "test_key_id");
options.set(CatalogOptions::DLF_ACCESS_KEY_SECRET, "test_key_secret");
let result = AuthProviderFactory::create_auth_provider(&options);
assert!(result.is_err());
}
#[test]
fn test_parse_region_from_uri() {
let region = DLFAuthProviderFactory::parse_region_from_uri(Some(
"http://cn-hangzhou-vpc.dlf.aliyuncs.com",
));
assert_eq!(region, Some("cn-hangzhou".to_string()));
}
#[test]
fn test_parse_signing_algo_from_uri() {
let algo = DLFAuthProviderFactory::parse_signing_algo_from_uri(Some(
"http://dlfnext.cn-hangzhou.aliyuncs.com",
));
assert_eq!(algo, DLFOpenApiSigner::IDENTIFIER);
let algo = DLFAuthProviderFactory::parse_signing_algo_from_uri(Some(
"http://cn-hangzhou-vpc.dlf.aliyuncs.com",
));
assert_eq!(algo, DLFDefaultSigner::IDENTIFIER);
}
#[test]
fn test_parse_signing_algo_from_uri_defaults_when_missing() {
let algo = DLFAuthProviderFactory::parse_signing_algo_from_uri(None);
assert_eq!(algo, DLFDefaultSigner::IDENTIFIER);
}
}