use awsim_core::{AwsError, RequestContext};
use serde_json::{Value, json};
use std::collections::HashMap;
use crate::{
error::{delete_conflict, entity_already_exists, malformed_policy_document, no_such_entity},
ids::{new_role_id, normalize_path, now_iso8601},
state::{IamState, Role},
};
fn validate_policy_document(doc: &str) -> Result<(), AwsError> {
awsim_iam_policy::parse(doc)
.map(|_| ())
.map_err(|e| malformed_policy_document(format!("Syntax errors in policy. {e}")))
}
use super::{opt_str, require_str};
fn role_to_value(r: &Role) -> Value {
let mut v = json!({
"RoleName": r.role_name,
"RoleId": r.role_id,
"Arn": r.arn,
"Path": r.path,
"AssumeRolePolicyDocument": r.assume_role_policy_document,
"CreateDate": r.create_date,
"MaxSessionDuration": r.max_session_duration,
});
if let Some(desc) = &r.description {
v["Description"] = Value::String(desc.clone());
}
v
}
pub fn create_role(
state: &IamState,
input: &Value,
ctx: &RequestContext,
) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let assume_role_policy = require_str(input, "AssumeRolePolicyDocument")?;
let path = normalize_path(opt_str(input, "Path"));
let description = opt_str(input, "Description").map(|s| s.to_string());
validate_policy_document(assume_role_policy)?;
if state.roles.contains_key(role_name) {
return Err(entity_already_exists("Role", role_name));
}
let role_id = new_role_id();
let arn = format!("arn:aws:iam::{}:role{}{}", ctx.account_id, path, role_name);
let max_session_duration = input
.get("MaxSessionDuration")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
.unwrap_or(3600);
let role = Role {
role_name: role_name.to_string(),
role_id,
arn,
path,
assume_role_policy_document: assume_role_policy.to_string(),
description,
create_date: now_iso8601(),
max_session_duration,
attached_policies: Vec::new(),
inline_policies: HashMap::new(),
tags: HashMap::new(),
};
let result = role_to_value(&role);
state.roles.insert(role_name.to_string(), role);
Ok(json!({ "Role": result }))
}
pub fn get_role(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let role = state
.roles
.get(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
Ok(json!({ "Role": role_to_value(&role) }))
}
pub fn delete_role(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
{
let role = state
.roles
.get(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
if !role.attached_policies.is_empty() {
return Err(delete_conflict(format!(
"Cannot delete role {role_name}: role has attached policies"
)));
}
}
for ip in state.instance_profiles.iter() {
if ip.roles.contains(&role_name.to_string()) {
return Err(delete_conflict(format!(
"Cannot delete role {role_name}: role is associated with instance profile {}",
ip.instance_profile_name
)));
}
}
state.roles.remove(role_name);
Ok(json!({}))
}
pub fn list_roles(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let path_prefix = opt_str(input, "PathPrefix").unwrap_or("/");
let roles: Vec<Value> = state
.roles
.iter()
.filter(|r| r.path.starts_with(path_prefix))
.map(|r| role_to_value(&r))
.collect();
Ok(json!({
"Roles": { "member": roles },
"IsTruncated": false,
}))
}
pub fn update_assume_role_policy(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let policy_document = require_str(input, "PolicyDocument")?;
validate_policy_document(policy_document)?;
let mut role = state
.roles
.get_mut(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
role.assume_role_policy_document = policy_document.to_string();
Ok(json!({}))
}
pub fn update_role(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let mut role = state
.roles
.get_mut(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
if let Some(desc) = opt_str(input, "Description") {
role.description = Some(desc.to_string());
}
if let Some(dur) = input.get("MaxSessionDuration").and_then(|v| v.as_u64()) {
role.max_session_duration = dur as u32;
}
Ok(json!({ "Role": role_to_value(&role) }))
}
pub fn update_role_description(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let description = require_str(input, "Description")?;
let mut role = state
.roles
.get_mut(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
role.description = Some(description.to_string());
Ok(json!({ "Role": role_to_value(&role) }))
}
pub fn put_role_permissions_boundary(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let boundary_arn = require_str(input, "PermissionsBoundary")?;
if !state.roles.contains_key(role_name) {
return Err(no_such_entity("Role", role_name));
}
state
.role_permissions_boundaries
.insert(role_name.to_string(), boundary_arn.to_string());
Ok(json!({}))
}
pub fn delete_role_permissions_boundary(
state: &IamState,
input: &Value,
) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
if !state.roles.contains_key(role_name) {
return Err(no_such_entity("Role", role_name));
}
state.role_permissions_boundaries.remove(role_name);
Ok(json!({}))
}
pub fn get_role_policy(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let policy_name = require_str(input, "PolicyName")?;
let role = state
.roles
.get(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
let doc = role
.inline_policies
.get(policy_name)
.ok_or_else(|| no_such_entity("InlinePolicy", policy_name))?
.clone();
Ok(json!({
"RoleName": role_name,
"PolicyName": policy_name,
"PolicyDocument": doc,
}))
}
pub fn delete_role_policy(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let policy_name = require_str(input, "PolicyName")?;
let mut role = state
.roles
.get_mut(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
if role.inline_policies.remove(policy_name).is_none() {
return Err(no_such_entity("InlinePolicy", policy_name));
}
Ok(json!({}))
}
pub fn list_role_policies(state: &IamState, input: &Value) -> Result<Value, AwsError> {
let role_name = require_str(input, "RoleName")?;
let role = state
.roles
.get(role_name)
.ok_or_else(|| no_such_entity("Role", role_name))?;
let names: Vec<Value> = role
.inline_policies
.keys()
.map(|k| Value::String(k.clone()))
.collect();
Ok(json!({
"PolicyNames": { "member": names },
"IsTruncated": false,
}))
}