diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index 709bd1cda..5c8633d4c 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -23,11 +23,11 @@ use crate::storage::head_prefix::{head_prefix_not_found_message, probe_prefix_ha use crate::storage::helper::OperationHelper; use crate::storage::options::{filter_object_metadata, get_content_sha256}; use crate::storage::readers::InMemoryAsyncReader; +use crate::storage::s3_api::acl::{build_get_bucket_acl_output, build_get_object_acl_output}; use crate::storage::s3_api::bucket::{ build_list_buckets_output, build_list_object_versions_output, build_list_objects_output, build_list_objects_v2_output, parse_list_object_versions_params, parse_list_objects_v2_params, }; -use crate::storage::s3_api::common::rustfs_owner; use crate::storage::s3_api::multipart::{ build_list_multipart_uploads_output, build_list_parts_output, parse_list_multipart_uploads_params, parse_list_parts_params, }; @@ -148,13 +148,7 @@ use s3s::header::{X_AMZ_RESTORE, X_AMZ_RESTORE_OUTPUT_PATH}; use s3s::{S3, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, dto::*, s3_error}; use std::convert::Infallible; use std::ops::Add; -use std::{ - collections::HashMap, - fmt::Debug, - path::Path, - str::FromStr, - sync::{Arc, LazyLock}, -}; +use std::{collections::HashMap, fmt::Debug, path::Path, str::FromStr, sync::Arc}; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -174,9 +168,6 @@ macro_rules! try_ { }; } -// Shared owner metadata source for S3 response compatibility. -pub(crate) static RUSTFS_OWNER: LazyLock = LazyLock::new(rustfs_owner); - #[derive(Debug, Clone)] pub struct FS { // pub store: ECStore, @@ -1957,21 +1948,7 @@ impl S3 for FS { .await .map_err(ApiError::from)?; - let grants = vec![Grant { - grantee: Some(Grantee { - type_: Type::from_static(Type::CANONICAL_USER), - display_name: None, - email_address: None, - id: None, - uri: None, - }), - permission: Some(Permission::from_static(Permission::FULL_CONTROL)), - }]; - - Ok(s3_response(GetBucketAclOutput { - grants: Some(grants), - owner: Some(RUSTFS_OWNER.to_owned()), - })) + Ok(s3_response(build_get_bucket_acl_output())) } #[instrument(level = "debug", skip(self))] @@ -2900,22 +2877,7 @@ impl S3 for FS { return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{e}"))); } - let grants = vec![Grant { - grantee: Some(Grantee { - type_: Type::from_static(Type::CANONICAL_USER), - display_name: None, - email_address: None, - id: None, - uri: None, - }), - permission: Some(Permission::from_static(Permission::FULL_CONTROL)), - }]; - - Ok(s3_response(GetObjectAclOutput { - grants: Some(grants), - owner: Some(RUSTFS_OWNER.to_owned()), - ..Default::default() - })) + Ok(s3_response(build_get_object_acl_output())) } async fn get_object_attributes( diff --git a/rustfs/src/storage/ecfs_test.rs b/rustfs/src/storage/ecfs_test.rs index f1a5c3b10..f373ea1db 100644 --- a/rustfs/src/storage/ecfs_test.rs +++ b/rustfs/src/storage/ecfs_test.rs @@ -16,7 +16,7 @@ mod tests { use crate::config::workload_profiles::WorkloadProfile; use crate::storage::ecfs::FS; - use crate::storage::ecfs::RUSTFS_OWNER; + use crate::storage::s3_api::common::rustfs_owner; use crate::storage::{ apply_cors_headers, check_preconditions, get_adaptive_buffer_size_with_profile, get_buffer_size_opt_in, is_etag_equal, matches_origin_pattern, parse_etag, parse_object_lock_legal_hold, parse_object_lock_retention, @@ -67,11 +67,12 @@ mod tests { } #[test] - fn test_rustfs_owner_constant() { - // Test that RUSTFS_OWNER constant is properly defined - assert!(!RUSTFS_OWNER.display_name.as_ref().unwrap().is_empty()); - assert!(!RUSTFS_OWNER.id.as_ref().unwrap().is_empty()); - assert_eq!(RUSTFS_OWNER.display_name.as_ref().unwrap(), "rustfs"); + fn test_rustfs_owner_helper() { + // Test that rustfs owner metadata remains stable for S3 compatibility. + let owner = rustfs_owner(); + assert!(!owner.display_name.as_ref().unwrap().is_empty()); + assert!(!owner.id.as_ref().unwrap().is_empty()); + assert_eq!(owner.display_name.as_ref().unwrap(), "rustfs"); } // Note: Most S3 API methods require complex setup with global state, storage backend, diff --git a/rustfs/src/storage/s3_api/acl.rs b/rustfs/src/storage/s3_api/acl.rs new file mode 100644 index 000000000..662af0549 --- /dev/null +++ b/rustfs/src/storage/s3_api/acl.rs @@ -0,0 +1,87 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::storage::s3_api::common::rustfs_owner; +use s3s::dto::{GetBucketAclOutput, GetObjectAclOutput, Grant, Grantee, Permission, Type}; + +fn full_control_grants() -> Vec { + vec![Grant { + grantee: Some(Grantee { + type_: Type::from_static(Type::CANONICAL_USER), + display_name: None, + email_address: None, + id: None, + uri: None, + }), + permission: Some(Permission::from_static(Permission::FULL_CONTROL)), + }] +} + +pub(crate) fn build_get_bucket_acl_output() -> GetBucketAclOutput { + GetBucketAclOutput { + grants: Some(full_control_grants()), + owner: Some(rustfs_owner()), + } +} + +pub(crate) fn build_get_object_acl_output() -> GetObjectAclOutput { + GetObjectAclOutput { + grants: Some(full_control_grants()), + owner: Some(rustfs_owner()), + ..Default::default() + } +} + +#[cfg(test)] +mod tests { + use super::{build_get_bucket_acl_output, build_get_object_acl_output}; + use crate::storage::s3_api::common::rustfs_owner; + use s3s::dto::{Permission, Type}; + + #[test] + fn test_build_get_bucket_acl_output_contains_full_control_grant_and_owner() { + let output = build_get_bucket_acl_output(); + let grants = output.grants.as_ref().expect("grants should be present"); + let owner = output.owner.as_ref().expect("owner should be present"); + + assert_eq!(grants.len(), 1); + assert_eq!(grants[0].permission.as_ref().map(Permission::as_str), Some(Permission::FULL_CONTROL)); + assert_eq!( + grants[0].grantee.as_ref().map(|grantee| grantee.type_.as_str()), + Some(Type::CANONICAL_USER) + ); + + let expected_owner = rustfs_owner(); + assert_eq!(owner.display_name, expected_owner.display_name); + assert_eq!(owner.id, expected_owner.id); + } + + #[test] + fn test_build_get_object_acl_output_contains_full_control_grant_and_owner() { + let output = build_get_object_acl_output(); + let grants = output.grants.as_ref().expect("grants should be present"); + let owner = output.owner.as_ref().expect("owner should be present"); + + assert_eq!(grants.len(), 1); + assert_eq!(grants[0].permission.as_ref().map(Permission::as_str), Some(Permission::FULL_CONTROL)); + assert_eq!( + grants[0].grantee.as_ref().map(|grantee| grantee.type_.as_str()), + Some(Type::CANONICAL_USER) + ); + + let expected_owner = rustfs_owner(); + assert_eq!(owner.display_name, expected_owner.display_name); + assert_eq!(owner.id, expected_owner.id); + } +} diff --git a/rustfs/src/storage/s3_api/mod.rs b/rustfs/src/storage/s3_api/mod.rs index ef0de522f..75f03fc86 100644 --- a/rustfs/src/storage/s3_api/mod.rs +++ b/rustfs/src/storage/s3_api/mod.rs @@ -17,7 +17,7 @@ //! This file intentionally starts as skeleton-only. Behavior remains in place //! until each helper is moved with dedicated small refactor steps. -pub(crate) mod acl {} +pub(crate) mod acl; pub(crate) mod bucket; pub(crate) mod common; pub(crate) mod encryption {}