use awsim_core::{AwsError, RequestContext};
use serde_json::{Value, json};
use crate::{
error::resource_not_found,
state::LambdaState,
util::{opt_str, require_str},
};
pub fn get_policy(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let func = state
.functions
.get(name)
.ok_or_else(|| resource_not_found("function", name))?;
if func.policy_statements.is_empty() {
return Err(AwsError::not_found(
"ResourceNotFoundException",
format!("No policy is associated with function: {name}"),
));
}
let statements: Vec<Value> = func.policy_statements.values().cloned().collect();
let policy = json!({
"Version": "2012-10-17",
"Id": "default",
"Statement": statements,
});
Ok(json!({ "Policy": policy.to_string(), "RevisionId": "1" }))
}
pub fn add_permission(
state: &LambdaState,
input: &Value,
ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let statement_id = require_str(input, "StatementId")?;
let mut func = state
.functions
.get_mut(name)
.ok_or_else(|| resource_not_found("function", name))?;
if func.policy_statements.contains_key(statement_id) {
return Err(AwsError::conflict(
"ResourceConflictException",
format!("Permission already exists with statement id: {statement_id}"),
));
}
let action = opt_str(input, "Action").unwrap_or("lambda:InvokeFunction");
let principal = opt_str(input, "Principal").unwrap_or("*");
let source_arn = input.get("SourceArn").and_then(|v| v.as_str());
let source_account = opt_str(input, "SourceAccount");
let function_arn = format!(
"arn:aws:lambda:{}:{}:function:{}",
ctx.region, ctx.account_id, name
);
let mut condition = serde_json::Map::new();
if let Some(arn) = source_arn {
condition.insert("ArnLike".to_string(), json!({ "AWS:SourceArn": arn }));
}
if let Some(acct) = source_account {
condition.insert(
"StringEquals".to_string(),
json!({ "AWS:SourceAccount": acct }),
);
}
let principal_value: Value = if principal == "*" {
Value::String("*".to_string())
} else if principal.starts_with("arn:aws:iam::") {
json!({ "AWS": principal })
} else if principal.len() == 12 && principal.chars().all(|c| c.is_ascii_digit()) {
json!({ "AWS": format!("arn:aws:iam::{principal}:root") })
} else {
json!({ "Service": principal })
};
let statement = json!({
"Sid": statement_id,
"Effect": "Allow",
"Principal": principal_value,
"Action": action,
"Resource": function_arn,
"Condition": condition,
});
func.policy_statements
.insert(statement_id.to_string(), statement.clone());
Ok(json!({ "Statement": statement.to_string() }))
}
pub fn remove_permission(
state: &LambdaState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = require_str(input, "FunctionName")?;
let statement_id = require_str(input, "StatementId")?;
let mut func = state
.functions
.get_mut(name)
.ok_or_else(|| resource_not_found("function", name))?;
func.policy_statements.remove(statement_id).ok_or_else(|| {
AwsError::not_found(
"ResourceNotFoundException",
format!("No permission with statement id: {statement_id}"),
)
})?;
Ok(json!({}))
}
pub fn get_account_settings(
_state: &LambdaState,
_input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
Ok(json!({
"AccountLimit": {
"TotalCodeSize": 80530636800i64,
"CodeSizeUnzipped": 262144000,
"CodeSizeZipped": 52428800,
"ConcurrentExecutions": 1000,
"UnreservedConcurrentExecutions": 1000,
},
"AccountUsage": {
"TotalCodeSize": 0,
"FunctionCount": 0,
},
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operations::functions::create_function;
use crate::state::LambdaState;
fn ctx() -> RequestContext {
RequestContext::new("lambda", "us-east-1")
}
fn empty_zip_b64() -> String {
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64;
let bytes: [u8; 22] = [
0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
BASE64.encode(bytes)
}
fn create_test_fn(state: &LambdaState) {
create_function(
state,
&json!({
"FunctionName": "f",
"Role": "arn:aws:iam::000000000000:role/test",
"Code": { "ZipFile": empty_zip_b64() },
}),
&ctx(),
)
.unwrap();
}
fn statement_principal(svc_state: &LambdaState, sid: &str) -> Value {
let f = svc_state.functions.get("f").unwrap();
let stmt = f.policy_statements.get(sid).unwrap().clone();
stmt["Principal"].clone()
}
#[test]
fn add_permission_wraps_service_principal_under_service_key() {
let state = LambdaState::default();
create_test_fn(&state);
add_permission(
&state,
&json!({
"FunctionName": "f",
"StatementId": "s1",
"Principal": "events.amazonaws.com",
}),
&ctx(),
)
.unwrap();
let p = statement_principal(&state, "s1");
assert_eq!(p["Service"], json!("events.amazonaws.com"));
assert!(p.get("AWS").is_none());
}
#[test]
fn add_permission_wraps_account_principal_as_aws_root_arn() {
let state = LambdaState::default();
create_test_fn(&state);
add_permission(
&state,
&json!({
"FunctionName": "f",
"StatementId": "s1",
"Principal": "111122223333",
}),
&ctx(),
)
.unwrap();
let p = statement_principal(&state, "s1");
assert_eq!(p["AWS"], json!("arn:aws:iam::111122223333:root"));
}
#[test]
fn add_permission_wraps_iam_arn_principal_as_aws() {
let state = LambdaState::default();
create_test_fn(&state);
add_permission(
&state,
&json!({
"FunctionName": "f",
"StatementId": "s1",
"Principal": "arn:aws:iam::111122223333:user/alice",
}),
&ctx(),
)
.unwrap();
let p = statement_principal(&state, "s1");
assert_eq!(p["AWS"], json!("arn:aws:iam::111122223333:user/alice"));
}
#[test]
fn add_permission_wildcard_principal_passes_through_as_string() {
let state = LambdaState::default();
create_test_fn(&state);
add_permission(
&state,
&json!({
"FunctionName": "f",
"StatementId": "s1",
"Principal": "*",
}),
&ctx(),
)
.unwrap();
let p = statement_principal(&state, "s1");
assert_eq!(p, json!("*"));
}
}