P3: Bucket Policy implementation complete
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

- BucketPolicy struct with Version + Statement array
- PolicyStatement: Effect, Principal, Action, Resource, Condition
- Principal matching (wildcard + user-specific)
- Action/Resource pattern matching with wildcards
- GetBucketPolicy: GET /s3/policy/:bucket
- PutBucketPolicy: PUT /s3/policy/:bucket
- DeleteBucketPolicy: DELETE /s3/policy/:bucket
- Policy persistence to data/s3_policies/:bucket/policy.json
- check_bucket_policy() for authorization
- 6 unit tests

Tests: 299 passed, 0 failed
This commit is contained in:
Warren
2026-06-21 22:50:53 +08:00
parent ca0f541a79
commit 02d98419e1
4 changed files with 346 additions and 0 deletions

View File

@@ -847,3 +847,92 @@ fn parse_complete_multipart_xml(xml: &[u8]) -> Option<Vec<(u32, String)>> {
Some(parts)
}
// ===== Bucket Policy Support =====
use crate::s3_policy::BucketPolicy;
static BUCKET_POLICIES: once_cell::sync::Lazy<Arc<RwLock<HashMap<String, BucketPolicy>>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
pub async fn get_bucket_policy(
Path(bucket): Path<String>,
State(_state): State<crate::server::AppState>,
) -> impl IntoResponse {
let policies = BUCKET_POLICIES.read().await;
let policy = policies.get(&bucket);
if policy.is_none() {
return (StatusCode::NOT_FOUND, "Bucket policy not found").into_response();
}
let policy = policy.unwrap();
let json = serde_json::to_string_pretty(policy)
.unwrap_or_else(|_| "{}".to_string());
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
(StatusCode::OK, headers, json).into_response()
}
pub async fn put_bucket_policy(
Path(bucket): Path<String>,
State(_state): State<crate::server::AppState>,
body: Body,
) -> impl IntoResponse {
let body_bytes = axum::body::to_bytes(body, 100000).await.ok();
if body_bytes.is_none() {
return (StatusCode::BAD_REQUEST, "Empty body").into_response();
}
let policy: BucketPolicy = match serde_json::from_slice(&body_bytes.unwrap()) {
Ok(p) => p,
Err(e) => return (StatusCode::BAD_REQUEST, format!("Invalid policy JSON: {}", e)).into_response(),
};
// Persist to file first (before moving policy)
let policy_path = format!("data/s3_policies/{}/policy.json", bucket);
if let Err(e) = std::fs::create_dir_all(format!("data/s3_policies/{}", bucket)) {
return (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create policy dir: {}", e)).into_response();
}
let policy_json = serde_json::to_string_pretty(&policy).unwrap_or_default();
if let Err(e) = std::fs::write(&policy_path, &policy_json) {
return (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to write policy: {}", e)).into_response();
}
// Now move policy to in-memory storage
{
let mut policies = BUCKET_POLICIES.write().await;
policies.insert(bucket.clone(), policy);
}
(StatusCode::NO_CONTENT, HeaderMap::new()).into_response()
}
pub async fn delete_bucket_policy(
Path(bucket): Path<String>,
State(_state): State<crate::server::AppState>,
) -> impl IntoResponse {
{
let mut policies = BUCKET_POLICIES.write().await;
policies.remove(&bucket);
}
let policy_path = format!("data/s3_policies/{}/policy.json", bucket);
let _ = std::fs::remove_file(&policy_path);
(StatusCode::NO_CONTENT, HeaderMap::new()).into_response()
}
pub fn check_bucket_policy(bucket: &str, action: &str, resource: &str, user_id: &str) -> bool {
let policies = BUCKET_POLICIES.blocking_read();
if let Some(policy) = policies.get(bucket) {
return policy.is_allowed(action, resource, user_id);
}
true
}