objectstore_server/auth/
service.rs

1use objectstore_service::id::{ObjectContext, ObjectId};
2use objectstore_service::service::{DeleteResponse, GetResponse, InsertResponse, MetadataResponse};
3use objectstore_service::{PayloadStream, StorageService};
4use objectstore_types::auth::Permission;
5use objectstore_types::metadata::Metadata;
6
7use crate::auth::AuthContext;
8use crate::endpoints::common::ApiResult;
9
10/// Wrapper around [`StorageService`] that ensures each operation is authorized.
11///
12/// Authorization is performed according to the request's authorization details, see also
13/// [`AuthContext`]. When [`crate::config::AuthZ::enforce`] is false, authorization failures are
14/// logged but any unauthorized operations are still allowed to proceed.
15///
16/// Objectstore API endpoints can use `AuthAwareService` simply by adding it to their handler
17/// function's argument list like so:
18///
19/// ```
20/// use axum::http::StatusCode;
21/// use objectstore_server::auth::AuthAwareService;
22///
23/// async fn my_endpoint(service: AuthAwareService) -> Result<StatusCode, StatusCode> {
24///     service.delete_object(todo!("pass some ID"))
25///         .await
26///         .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
27///
28///     Ok(StatusCode::NO_CONTENT)
29/// }
30/// ```
31#[derive(Debug)]
32pub struct AuthAwareService {
33    service: StorageService,
34    context: Option<AuthContext>,
35}
36
37impl AuthAwareService {
38    /// Creates a new `AuthAwareService` using the given service and auth context.
39    ///
40    /// If no auth context is provided, authorization is disabled and all operations will be
41    /// permitted.
42    pub fn new(service: StorageService, context: Option<AuthContext>) -> Self {
43        Self { service, context }
44    }
45
46    fn assert_authorized(&self, perm: Permission, context: &ObjectContext) -> ApiResult<()> {
47        if let Some(auth) = &self.context {
48            auth.assert_authorized(perm, context)?;
49        }
50
51        Ok(())
52    }
53
54    /// Checks whether the request is authorized for the given permission on the given context.
55    ///
56    /// Returns `Ok(())` if authorized, or otherwise an error indicating the reason.
57    /// Equivalent to the internal `assert_authorized` check but exposed for callers
58    /// that validate operations individually before delegating to a lower-level service.
59    pub fn check_permission(&self, perm: Permission, context: &ObjectContext) -> ApiResult<()> {
60        self.assert_authorized(perm, context)
61    }
62
63    /// Auth-aware wrapper around [`StorageService::insert_object`].
64    pub async fn insert_object(
65        &self,
66        context: ObjectContext,
67        key: Option<String>,
68        metadata: Metadata,
69        stream: PayloadStream,
70    ) -> ApiResult<InsertResponse> {
71        self.assert_authorized(Permission::ObjectWrite, &context)?;
72        Ok(self
73            .service
74            .insert_object(context, key, metadata, stream)
75            .await?)
76    }
77
78    /// Auth-aware wrapper around [`StorageService::get_metadata`].
79    pub async fn get_metadata(&self, id: ObjectId) -> ApiResult<MetadataResponse> {
80        self.assert_authorized(Permission::ObjectRead, id.context())?;
81        Ok(self.service.get_metadata(id).await?)
82    }
83
84    /// Auth-aware wrapper around [`StorageService::get_object`].
85    pub async fn get_object(&self, id: ObjectId) -> ApiResult<GetResponse> {
86        self.assert_authorized(Permission::ObjectRead, id.context())?;
87        Ok(self.service.get_object(id).await?)
88    }
89
90    /// Auth-aware wrapper around [`StorageService::delete_object`].
91    pub async fn delete_object(&self, id: ObjectId) -> ApiResult<DeleteResponse> {
92        self.assert_authorized(Permission::ObjectDelete, id.context())?;
93        Ok(self.service.delete_object(id).await?)
94    }
95}