use awsim_core::{AwsError, RequestContext};
use serde_json::{Value, json};
use std::collections::HashMap;
use crate::{
error::{delete_conflict, entity_already_exists, no_such_entity},
ids::{new_group_id, normalize_path, now_iso8601},
state::{Group, IamState},
};
use super::{opt_str, require_str};
fn group_to_value(g: &Group) -> Value {
json!({
"GroupName": g.group_name,
"GroupId": g.group_id,
"Arn": g.arn,
"Path": g.path,
"CreateDate": g.create_date,
})
}
pub fn create_group(
state: &IamState,
input: &Value,
ctx: &RequestContext,
) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let path = normalize_path(opt_str(input, "Path"));
if state.groups.contains_key(group_name) {
return Err(entity_already_exists("Group", group_name));
}
let group_id = new_group_id();
let arn = format!(
"arn:aws:iam::{}:group{}{}",
ctx.account_id, path, group_name
);
let group = Group {
group_name: group_name.to_string(),
group_id,
arn,
path,
create_date: now_iso8601(),
members: Vec::new(),
attached_policies: Vec::new(),
inline_policies: HashMap::new(),
tags: HashMap::new(),
};
let result = group_to_value(&group);
state.groups.insert(group_name.to_string(), group);
Ok(json!({ "Group": result }))
}
pub fn get_group(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let group = state
.groups
.get(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
let users: Vec<Value> = group
.members
.iter()
.filter_map(|uname| {
state.users.get(uname).map(|u| {
json!({
"UserName": u.user_name,
"UserId": u.user_id,
"Arn": u.arn,
"Path": u.path,
"CreateDate": u.create_date,
})
})
})
.collect();
Ok(json!({
"Group": group_to_value(&group),
"Users": { "member": users },
"IsTruncated": false,
}))
}
pub fn delete_group(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
{
let group = state
.groups
.get(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
if !group.members.is_empty() {
return Err(delete_conflict(format!(
"Cannot delete group {group_name}: group has members"
)));
}
if !group.attached_policies.is_empty() {
return Err(delete_conflict(format!(
"Cannot delete group {group_name}: group has attached policies"
)));
}
}
state.groups.remove(group_name);
Ok(json!({}))
}
pub fn list_groups(state: &IamState, _input: &Value) -> Result<Value, AwsError> {
let groups: Vec<Value> = state.groups.iter().map(|g| group_to_value(&g)).collect();
Ok(json!({
"Groups": { "member": groups },
"IsTruncated": false,
}))
}
pub fn add_user_to_group(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let user_name = require_str(input, "UserName")?;
if !state.users.contains_key(user_name) {
return Err(no_such_entity("User", user_name));
}
{
let mut group = state
.groups
.get_mut(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
if !group.members.contains(&user_name.to_string()) {
group.members.push(user_name.to_string());
}
}
if let Some(mut user) = state.users.get_mut(user_name)
&& !user.groups.contains(&group_name.to_string())
{
user.groups.push(group_name.to_string());
}
Ok(json!({}))
}
pub fn remove_user_from_group(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let user_name = require_str(input, "UserName")?;
{
let mut group = state
.groups
.get_mut(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
let before = group.members.len();
group.members.retain(|m| m != user_name);
if group.members.len() == before {
return Err(no_such_entity(
"User in group",
&format!("{user_name} in {group_name}"),
));
}
}
if let Some(mut user) = state.users.get_mut(user_name) {
user.groups.retain(|g| g != group_name);
}
Ok(json!({}))
}
pub fn get_group_policy(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let policy_name = require_str(input, "PolicyName")?;
let group = state
.groups
.get(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
let doc = group
.inline_policies
.get(policy_name)
.ok_or_else(|| no_such_entity("InlinePolicy", policy_name))?
.clone();
Ok(json!({
"GroupName": group_name,
"PolicyName": policy_name,
"PolicyDocument": doc,
}))
}
pub fn delete_group_policy(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let policy_name = require_str(input, "PolicyName")?;
let mut group = state
.groups
.get_mut(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
if group.inline_policies.remove(policy_name).is_none() {
return Err(no_such_entity("InlinePolicy", policy_name));
}
Ok(json!({}))
}
pub fn list_group_policies(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let group = state
.groups
.get(group_name)
.ok_or_else(|| no_such_entity("Group", group_name))?;
let names: Vec<Value> = group
.inline_policies
.keys()
.map(|k| Value::String(k.clone()))
.collect();
Ok(json!({
"PolicyNames": { "member": names },
"IsTruncated": false,
}))
}
pub fn update_group(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let group_name = require_str(input, "GroupName")?;
let new_group_name = opt_str(input, "NewGroupName");
let new_path = opt_str(input, "NewPath");
if !state.groups.contains_key(group_name) {
return Err(no_such_entity("Group", group_name));
}
if let Some(new_name) = new_group_name
&& new_name != group_name
&& state.groups.contains_key(new_name)
{
return Err(entity_already_exists("Group", new_name));
}
if new_group_name.is_none() && new_path.is_none() {
return Ok(json!({}));
}
let (_, mut group) = state.groups.remove(group_name).unwrap();
if let Some(np) = new_path {
group.path = normalize_path(Some(np));
}
let final_name = if let Some(nn) = new_group_name {
group.group_name = nn.to_string();
nn.to_string()
} else {
group_name.to_string()
};
state.groups.insert(final_name, group);
Ok(json!({}))
}