P3: Bucket Policy implementation complete
- 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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user