use std::sync::Arc;
use object_store::DynObjectStore;
use unitycatalog_object_store::{
PathOperation, TableOperation, UnityObjectStoreFactory, VolumeOperation,
};
use crate::backend::{
ForwardedUser, ProxyCapabilities, ProxyReq, Securable, StorageProxyBackend,
reject_key_traversal,
};
use crate::error::{ProxyError, ProxyResult};
pub const DEFAULT_FORWARDED_USER_HEADER: &str = "x-forwarded-user";
#[derive(Clone)]
pub struct UnityFactoryProxyBackend {
factory: UnityObjectStoreFactory,
forwarded_header: String,
}
impl UnityFactoryProxyBackend {
pub async fn connect(base_uri: impl Into<String>, token: Option<String>) -> ProxyResult<Self> {
Self::connect_with_forwarded_header(base_uri, token, DEFAULT_FORWARDED_USER_HEADER).await
}
pub async fn connect_with_forwarded_header(
base_uri: impl Into<String>,
token: Option<String>,
forwarded_header: impl Into<String>,
) -> ProxyResult<Self> {
let allow_unauthenticated = token.is_none();
let factory = UnityObjectStoreFactory::builder()
.with_uri(base_uri)
.with_token(token)
.with_allow_unauthenticated(allow_unauthenticated)
.build()
.await
.map_err(|e| ProxyError::Internal(format!("connect UC factory: {e}")))?;
Ok(Self {
factory,
forwarded_header: forwarded_header.into(),
})
}
pub fn from_factory(factory: UnityObjectStoreFactory) -> Self {
Self {
factory,
forwarded_header: DEFAULT_FORWARDED_USER_HEADER.to_string(),
}
}
}
#[async_trait::async_trait]
impl<Cx: ForwardedUser + Send + Sync + 'static> StorageProxyBackend<Cx>
for UnityFactoryProxyBackend
{
fn capabilities(&self) -> ProxyCapabilities {
ProxyCapabilities { enabled: true }
}
async fn authorize(&self, req: &ProxyReq, _cx: &Cx) -> ProxyResult<()> {
reject_key_traversal(&req.key)
}
async fn open(&self, req: &ProxyReq, cx: &Cx) -> ProxyResult<Arc<DynObjectStore>> {
let factory = self
.factory
.with_forwarded_user(&self.forwarded_header, cx.forwarded_user())
.map_err(ProxyError::Storage)?;
let write = req.verb.is_write();
let store = match &req.securable {
Securable::Table { full_name } => {
let op = if write {
TableOperation::ReadWrite
} else {
TableOperation::Read
};
factory.for_table(full_name.clone(), op).await
}
Securable::Volume { full_name } => {
let op = if write {
VolumeOperation::ReadWrite
} else {
VolumeOperation::Read
};
factory.for_volume(full_name.clone(), op).await
}
Securable::Path { url } => {
let op = if write {
PathOperation::ReadWrite
} else {
PathOperation::Read
};
factory.for_path(url, op).await
}
}
.map_err(ProxyError::Storage)?;
Ok(store.as_dyn())
}
}