diff --git a/crates/iam/src/manager.rs b/crates/iam/src/manager.rs index c42b88561..2b4363dfb 100644 --- a/crates/iam/src/manager.rs +++ b/crates/iam/src/manager.rs @@ -1728,6 +1728,15 @@ where } let u = m[name].clone(); + match user_type { + UserType::Sts => { + Cache::add_or_update(&self.cache.sts_accounts, name, &u, OffsetDateTime::now_utc()); + } + UserType::Reg | UserType::Svc => { + Cache::add_or_update(&self.cache.users, name, &u, OffsetDateTime::now_utc()); + } + UserType::None => {} + } match user_type { UserType::Sts => { @@ -1752,7 +1761,7 @@ where return Ok(()); } - Cache::add_or_update(&self.cache.sts_policies, name, &m[name], OffsetDateTime::now_utc()); + Cache::add_or_update(&self.cache.user_policies, name, &m[name], OffsetDateTime::now_utc()); } UserType::Svc => { diff --git a/crates/iam/src/sys.rs b/crates/iam/src/sys.rs index ce13271e9..1924971f5 100644 --- a/crates/iam/src/sys.rs +++ b/crates/iam/src/sys.rs @@ -1083,7 +1083,16 @@ mod tests { Err(Error::InvalidArgument) } - async fn load_user(&self, _name: &str, _user_type: UserType, _m: &mut HashMap) -> Result<()> { + async fn load_user(&self, name: &str, user_type: UserType, m: &mut HashMap) -> Result<()> { + if user_type == UserType::Reg && name == "notify-user" { + let user = UserIdentity::from(Credentials { + access_key: name.to_string(), + secret_key: "notify-user-secret".to_string(), + status: ACCOUNT_ON.to_string(), + ..Default::default() + }); + m.insert(name.to_string(), user); + } Ok(()) } @@ -1148,11 +1157,14 @@ mod tests { async fn load_mapped_policy( &self, - _name: &str, - _user_type: UserType, - _is_group: bool, - _m: &mut HashMap, + name: &str, + user_type: UserType, + is_group: bool, + m: &mut HashMap, ) -> Result<()> { + if user_type == UserType::Reg && !is_group && name == "notify-user" { + m.insert(name.to_string(), MappedPolicy::new("readwrite")); + } Ok(()) } @@ -1247,4 +1259,29 @@ mod tests { "STS temp credentials with no groups in args should still be allowed via parent user's group policy (readwrite)" ); } + + /// Regression test for cross-node IAM notifications: + /// `load_user` must populate user cache, and regular-user mapped policy must be written to + /// `user_policies` (not `sts_policies`), otherwise list-users and bucket-scoped user listing + /// may miss users on follower nodes. + #[tokio::test] + async fn test_load_user_notification_populates_user_and_policy_caches() { + let store = StsGroupsFallbackMockStore; + let cache_manager = IamCache::new(store).await; + let iam_sys = IamSys::new(cache_manager); + + iam_sys.load_user("notify-user", UserType::Reg).await.unwrap(); + + let users = iam_sys.list_users().await.unwrap(); + assert!( + users.contains_key("notify-user"), + "regular user loaded via notification must appear in list_users cache view" + ); + + let bucket_users = iam_sys.list_bucket_users("notification-regression-bucket").await.unwrap(); + assert!( + bucket_users.contains_key("notify-user"), + "regular user mapped policy must be written to user_policies for bucket user listing" + ); + } }