Skip to main content

unitycatalog_server/services/
mod.rs

1use std::sync::Arc;
2
3use crate::Result;
4use crate::policy::{Decision, Permission, Policy, ProvidesPolicy};
5use crate::store::{ProvidesResourceStore, ResourceStore};
6use unitycatalog_common::models::ResourceIdent;
7use unitycatalog_delta_api::coordinator::{
8    CommitCoordinator, InMemoryCommitCoordinator, ProvidesCommitCoordinator,
9};
10
11pub mod credential_vending;
12mod delta_backend;
13pub mod location;
14pub mod location_policy;
15pub(crate) mod object_store;
16
17pub use location_policy::LocalStoragePolicy;
18
19/// Access to the server's [`LocalStoragePolicy`].
20///
21/// Implemented by the server handler so request handlers (defined as blanket
22/// impls over a generic `T`) can reach the server-wide allowlist that governs
23/// which host paths may back a `file://` storage location. Mirrors
24/// [`ProvidesCommitCoordinator`].
25pub trait ProvidesLocalStoragePolicy {
26    fn local_storage_policy(&self) -> &LocalStoragePolicy;
27}
28
29/// Access to the server's metastore-level managed storage root.
30///
31/// This is the default managed storage location for the metastore as a whole.
32/// A managed catalog created without an explicit `storage_root` inherits this
33/// root, mirroring the Unity Catalog metastore → catalog → schema hierarchy
34/// ("if the metastore has no managed storage set, you must set one at the
35/// catalog level"). `None` means no metastore default is configured, in which
36/// case every managed catalog must carry its own `storage_root`.
37pub trait ProvidesManagedStorageRoot {
38    fn managed_storage_root(&self) -> Option<&str>;
39}
40
41#[derive(Clone)]
42pub struct ServerHandler<Cx> {
43    handler: Arc<ServerHandlerInner<Cx>>,
44}
45
46impl<Cx: Send + Sync + 'static> ServerHandler<Cx> {
47    pub fn try_new_tokio(
48        policy: Arc<dyn Policy<Cx>>,
49        store: Arc<dyn ResourceStore>,
50    ) -> Result<Self> {
51        Self::try_new_tokio_with_coordinator(
52            policy,
53            store,
54            Arc::new(InMemoryCommitCoordinator::default()),
55        )
56    }
57
58    /// Construct a handler backed by a specific [`CommitCoordinator`].
59    ///
60    /// Use this to wire a persistent coordinator (e.g. the Postgres-backed
61    /// `GraphStore`) instead of the default in-memory one.
62    pub fn try_new_tokio_with_coordinator(
63        policy: Arc<dyn Policy<Cx>>,
64        store: Arc<dyn ResourceStore>,
65        commit_coordinator: Arc<dyn CommitCoordinator>,
66    ) -> Result<Self> {
67        let handler = Arc::new(
68            ServerHandlerInner::new(policy.clone(), store.clone())
69                .with_commit_coordinator(commit_coordinator),
70        );
71        Ok(Self { handler })
72    }
73}
74
75impl<Cx: Send + Sync + 'static> ServerHandler<Cx> {
76    /// Set the allowlist governing `file://` storage locations.
77    ///
78    /// Rebuilds the inner handler with the policy attached. Call at construction
79    /// time, before the handler is cloned/shared. When unset, all local storage
80    /// is denied.
81    pub fn with_local_storage_policy(mut self, policy: impl Into<Arc<LocalStoragePolicy>>) -> Self {
82        // Rebuild the inner handler with the policy attached. All inner fields
83        // are `Arc`s, so this is a cheap reconstruction (the derived `Clone`
84        // would require `Cx: Clone`, which we don't impose).
85        let prev = &self.handler;
86        let inner = ServerHandlerInner {
87            policy: prev.policy.clone(),
88            store: prev.store.clone(),
89            commit_coordinator: prev.commit_coordinator.clone(),
90            local_storage_policy: policy.into(),
91            managed_storage_root: prev.managed_storage_root.clone(),
92        };
93        self.handler = Arc::new(inner);
94        self
95    }
96
97    /// Set the metastore-level managed storage root.
98    ///
99    /// Rebuilds the inner handler with the root attached. Call at construction
100    /// time, before the handler is cloned/shared. When unset, managed catalogs
101    /// must each supply their own `storage_root`.
102    pub fn with_managed_storage_root(mut self, root: Option<impl Into<Arc<str>>>) -> Self {
103        let prev = &self.handler;
104        let inner = ServerHandlerInner {
105            policy: prev.policy.clone(),
106            store: prev.store.clone(),
107            commit_coordinator: prev.commit_coordinator.clone(),
108            local_storage_policy: prev.local_storage_policy.clone(),
109            managed_storage_root: root.map(Into::into),
110        };
111        self.handler = Arc::new(inner);
112        self
113    }
114}
115
116#[derive(Clone)]
117pub struct ServerHandlerInner<Cx> {
118    policy: Arc<dyn Policy<Cx>>,
119    store: Arc<dyn ResourceStore>,
120    /// Delta catalog-managed commit coordinator (in-memory by default).
121    commit_coordinator: Arc<dyn CommitCoordinator>,
122    /// Allowlist governing which host paths may back a `file://` storage
123    /// location. Deny-all by default (see [`LocalStoragePolicy`]).
124    local_storage_policy: Arc<LocalStoragePolicy>,
125    /// Metastore-level managed storage root inherited by managed catalogs that
126    /// omit `storage_root`. `None` ⇒ no metastore default (see
127    /// [`ProvidesManagedStorageRoot`]).
128    managed_storage_root: Option<Arc<str>>,
129}
130
131impl<Cx: Send + Sync + 'static> ServerHandlerInner<Cx> {
132    pub fn new(policy: Arc<dyn Policy<Cx>>, store: Arc<dyn ResourceStore>) -> Self {
133        Self {
134            policy,
135            store,
136            commit_coordinator: Arc::new(InMemoryCommitCoordinator::default()),
137            // Deny all local (file://) storage until a policy is configured.
138            local_storage_policy: Arc::new(LocalStoragePolicy::deny_all()),
139            // No metastore-level managed storage root by default.
140            managed_storage_root: None,
141        }
142    }
143
144    /// Set the allowlist governing `file://` storage locations.
145    ///
146    /// When unset, the handler denies every local storage path.
147    pub fn with_local_storage_policy(mut self, policy: impl Into<Arc<LocalStoragePolicy>>) -> Self {
148        self.local_storage_policy = policy.into();
149        self
150    }
151
152    /// Set the metastore-level managed storage root.
153    ///
154    /// When unset, managed catalogs must each supply their own `storage_root`.
155    pub fn with_managed_storage_root(mut self, root: Option<impl Into<Arc<str>>>) -> Self {
156        self.managed_storage_root = root.map(Into::into);
157        self
158    }
159
160    /// Override the Delta commit coordinator (e.g. a Postgres-backed one, or a
161    /// custom unbackfilled cap).
162    pub fn with_commit_coordinator(mut self, coordinator: Arc<dyn CommitCoordinator>) -> Self {
163        self.commit_coordinator = coordinator;
164        self
165    }
166}
167
168impl<Cx: Send + Sync + 'static> ProvidesPolicy<Cx> for ServerHandlerInner<Cx> {
169    fn policy(&self) -> &Arc<dyn Policy<Cx>> {
170        &self.policy
171    }
172}
173
174impl<Cx: Send + Sync + 'static> ProvidesPolicy<Cx> for ServerHandler<Cx> {
175    fn policy(&self) -> &Arc<dyn Policy<Cx>> {
176        &self.handler.policy
177    }
178}
179
180#[async_trait::async_trait]
181impl<Cx: Send + Sync + 'static> Policy<Cx> for ServerHandlerInner<Cx> {
182    async fn authorize(
183        &self,
184        resource: &ResourceIdent,
185        permission: &Permission,
186        context: &Cx,
187    ) -> Result<Decision> {
188        self.policy().authorize(resource, permission, context).await
189    }
190
191    async fn authorize_many(
192        &self,
193        resources: &[ResourceIdent],
194        permission: &Permission,
195        context: &Cx,
196    ) -> Result<Vec<Decision>> {
197        self.policy()
198            .authorize_many(resources, permission, context)
199            .await
200    }
201}
202
203#[async_trait::async_trait]
204impl<Cx: Send + Sync + 'static> Policy<Cx> for ServerHandler<Cx> {
205    async fn authorize(
206        &self,
207        resource: &ResourceIdent,
208        permission: &Permission,
209        context: &Cx,
210    ) -> Result<Decision> {
211        self.handler
212            .policy
213            .authorize(resource, permission, context)
214            .await
215    }
216
217    async fn authorize_many(
218        &self,
219        resources: &[ResourceIdent],
220        permission: &Permission,
221        context: &Cx,
222    ) -> Result<Vec<Decision>> {
223        self.handler
224            .policy
225            .authorize_many(resources, permission, context)
226            .await
227    }
228}
229
230impl<Cx: Send + Sync + 'static> ProvidesResourceStore for ServerHandlerInner<Cx> {
231    fn store(&self) -> &dyn ResourceStore {
232        self.store.as_ref()
233    }
234}
235
236impl<Cx: Send + Sync + 'static> ProvidesResourceStore for ServerHandler<Cx> {
237    fn store(&self) -> &dyn ResourceStore {
238        self.handler.store.as_ref()
239    }
240}
241
242impl<Cx: Send + Sync + 'static> ProvidesCommitCoordinator for ServerHandlerInner<Cx> {
243    fn commit_coordinator(&self) -> &dyn CommitCoordinator {
244        self.commit_coordinator.as_ref()
245    }
246}
247
248impl<Cx: Send + Sync + 'static> ProvidesCommitCoordinator for ServerHandler<Cx> {
249    fn commit_coordinator(&self) -> &dyn CommitCoordinator {
250        self.handler.commit_coordinator.as_ref()
251    }
252}
253
254impl<Cx: Send + Sync + 'static> ProvidesLocalStoragePolicy for ServerHandlerInner<Cx> {
255    fn local_storage_policy(&self) -> &LocalStoragePolicy {
256        self.local_storage_policy.as_ref()
257    }
258}
259
260impl<Cx: Send + Sync + 'static> ProvidesLocalStoragePolicy for ServerHandler<Cx> {
261    fn local_storage_policy(&self) -> &LocalStoragePolicy {
262        self.handler.local_storage_policy.as_ref()
263    }
264}
265
266impl<Cx: Send + Sync + 'static> ProvidesManagedStorageRoot for ServerHandlerInner<Cx> {
267    fn managed_storage_root(&self) -> Option<&str> {
268        self.managed_storage_root.as_deref()
269    }
270}
271
272impl<Cx: Send + Sync + 'static> ProvidesManagedStorageRoot for ServerHandler<Cx> {
273    fn managed_storage_root(&self) -> Option<&str> {
274        self.handler.managed_storage_root.as_deref()
275    }
276}