use std::sync::Arc;
use fakecloud_core::auth::ResourcePolicyProvider;
use crate::state::SharedEventBridgeState;
pub struct EventBridgeResourcePolicyProvider {
state: SharedEventBridgeState,
}
impl EventBridgeResourcePolicyProvider {
pub fn new(state: SharedEventBridgeState) -> Self {
Self { state }
}
pub fn shared(state: SharedEventBridgeState) -> Arc<dyn ResourcePolicyProvider> {
Arc::new(Self::new(state))
}
}
impl ResourcePolicyProvider for EventBridgeResourcePolicyProvider {
fn resource_policy(&self, service: &str, resource_arn: &str) -> Option<String> {
if !service.eq_ignore_ascii_case("events") {
return None;
}
if !is_event_bus_arn(resource_arn) {
return None;
}
let accts = self.state.read();
let acct = resource_arn.split(':').nth(4).unwrap_or("");
let state = accts.get(acct).unwrap_or_else(|| accts.default_ref());
state
.buses
.values()
.find(|b| b.arn == resource_arn)
.and_then(|b| b.policy.as_ref())
.map(|p| p.to_string())
}
}
fn is_event_bus_arn(arn: &str) -> bool {
let Some(rest) = arn.strip_prefix("arn:aws:events:") else {
return false;
};
let parts: Vec<&str> = rest.splitn(3, ':').collect();
if parts.len() != 3 {
return false;
}
if parts[0].is_empty() || parts[1].is_empty() {
return false;
}
let resource = parts[2];
if let Some(name) = resource.strip_prefix("event-bus/") {
!name.is_empty()
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{EventBridgeState, EventBus};
use chrono::Utc;
use fakecloud_core::multi_account::MultiAccountState;
use parking_lot::RwLock;
use serde_json::json;
use std::collections::BTreeMap;
fn state_with_bus(arn: &str, policy: Option<serde_json::Value>) -> SharedEventBridgeState {
let state = Arc::new(RwLock::new(MultiAccountState::<EventBridgeState>::new(
"123456789012",
"us-east-1",
"http://localhost:4566",
)));
let name = arn
.rsplit_once("event-bus/")
.map(|(_, n)| n)
.unwrap_or("default")
.to_string();
state.write().default_mut().buses.insert(
name.clone(),
EventBus {
name: name.clone(),
arn: arn.to_string(),
description: None,
policy,
tags: BTreeMap::new(),
creation_time: Utc::now(),
last_modified_time: Utc::now(),
kms_key_identifier: None,
dead_letter_config: None,
},
);
state
}
#[test]
fn returns_stored_policy_for_event_bus_arn() {
let policy = json!({"Version":"2012-10-17","Statement":[]});
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/my-bus";
let state = state_with_bus(arn, Some(policy.clone()));
let provider = EventBridgeResourcePolicyProvider::new(state);
let raw = provider.resource_policy("events", arn).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&raw).unwrap();
assert_eq!(parsed, policy);
}
#[test]
fn returns_none_when_bus_has_no_policy() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/my-bus";
let state = state_with_bus(arn, None);
let provider = EventBridgeResourcePolicyProvider::new(state);
assert_eq!(provider.resource_policy("events", arn), None);
}
#[test]
fn returns_none_when_bus_missing() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/other";
let state = state_with_bus(arn, Some(json!({})));
let provider = EventBridgeResourcePolicyProvider::new(state);
assert_eq!(
provider.resource_policy(
"events",
"arn:aws:events:us-east-1:123456789012:event-bus/my-bus"
),
None
);
}
#[test]
fn returns_none_for_non_events_service_prefix() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/b";
let state = state_with_bus(arn, Some(json!({})));
let provider = EventBridgeResourcePolicyProvider::new(state);
assert_eq!(provider.resource_policy("sns", arn), None);
assert_eq!(provider.resource_policy("sqs", arn), None);
}
#[test]
fn service_prefix_match_is_case_insensitive() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/b";
let state = state_with_bus(arn, Some(json!({})));
let provider = EventBridgeResourcePolicyProvider::new(state);
assert!(provider.resource_policy("EVENTS", arn).is_some());
}
#[test]
fn returns_none_for_malformed_arn() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/b";
let state = state_with_bus(arn, Some(json!({})));
let provider = EventBridgeResourcePolicyProvider::new(state);
assert_eq!(provider.resource_policy("events", ""), None);
assert_eq!(provider.resource_policy("events", "not-an-arn"), None);
assert_eq!(provider.resource_policy("events", "arn:aws:events:"), None);
assert_eq!(
provider.resource_policy(
"events",
"arn:aws:events:us-east-1:123456789012:rule/my-rule"
),
None
);
}
#[test]
fn is_event_bus_arn_rejects_empty_segments() {
assert!(!is_event_bus_arn("arn:aws:events:::event-bus/b"));
assert!(!is_event_bus_arn("arn:aws:events:us-east-1::event-bus/b"));
assert!(!is_event_bus_arn(
"arn:aws:events:us-east-1:123456789012:event-bus/"
));
}
#[test]
fn shared_constructor_wraps_in_arc() {
let arn = "arn:aws:events:us-east-1:123456789012:event-bus/b";
let state = state_with_bus(arn, Some(json!({"x": 1})));
let arc = EventBridgeResourcePolicyProvider::shared(state);
let raw = arc.resource_policy("events", arn).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&raw).unwrap();
assert_eq!(parsed, json!({"x": 1}));
}
}