bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
use super::*;

pub(crate) fn parse_bucket_policy_json(json: &str) -> Result<Policy, RuntimeError> {
    let value: serde_json::Value = serde_json::from_str(json)
        .map_err(|error| RuntimeError::InvalidBucketPolicy(error.to_string()))?;
    let statements = value
        .get("Statement")
        .ok_or_else(|| RuntimeError::InvalidBucketPolicy("missing Statement".to_string()))?;
    let statement_values = match statements {
        serde_json::Value::Array(values) => values.clone(),
        serde_json::Value::Object(_) => vec![statements.clone()],
        _ => {
            return Err(RuntimeError::InvalidBucketPolicy(
                "Statement must be an object or array".to_string(),
            ))
        }
    };
    let mut policy = Policy::new();
    for statement_value in statement_values {
        let statement_object = statement_value.as_object().ok_or_else(|| {
            RuntimeError::InvalidBucketPolicy("Statement entries must be objects".to_string())
        })?;
        let effect = match statement_object
            .get("Effect")
            .and_then(serde_json::Value::as_str)
        {
            Some("Allow") => Effect::Allow,
            Some("Deny") => Effect::Deny,
            Some(value) => {
                return Err(RuntimeError::InvalidBucketPolicy(format!(
                    "unsupported Effect: {value}"
                )))
            }
            None => {
                return Err(RuntimeError::InvalidBucketPolicy(
                    "missing Effect".to_string(),
                ))
            }
        };
        let principals = statement_object
            .get("Principal")
            .map(policy_principal_values)
            .transpose()?
            .unwrap_or_else(|| vec!["*".to_string()]);
        let actions = policy_string_values(
            statement_object
                .get("Action")
                .ok_or_else(|| RuntimeError::InvalidBucketPolicy("missing Action".to_string()))?,
            "Action",
        )?;
        let resources = policy_string_values(
            statement_object
                .get("Resource")
                .ok_or_else(|| RuntimeError::InvalidBucketPolicy("missing Resource".to_string()))?,
            "Resource",
        )?;
        let mut statement = match effect {
            Effect::Allow => Statement::allow_many(principals, actions, resources),
            Effect::Deny => Statement::deny_many(principals, actions, resources),
        };
        if let Some(string_equals) = statement_object
            .get("Condition")
            .and_then(|condition| condition.get("StringEquals"))
            .and_then(serde_json::Value::as_object)
        {
            for (key, value) in string_equals {
                let values = policy_string_values(value, &format!("StringEquals.{key}"))?;
                statement = statement.with_string_equals_any(key.clone(), values);
            }
        }
        policy.add(statement);
    }
    Ok(policy)
}

pub(crate) fn policy_principal_values(
    value: &serde_json::Value,
) -> Result<Vec<String>, RuntimeError> {
    match value {
        serde_json::Value::Object(object) => object
            .get("AWS")
            .map(|value| policy_string_values(value, "Principal.AWS"))
            .unwrap_or_else(|| Ok(vec!["*".to_string()])),
        _ => policy_string_values(value, "Principal"),
    }
}

pub(crate) fn policy_string_values(
    value: &serde_json::Value,
    field: &str,
) -> Result<Vec<String>, RuntimeError> {
    match value {
        serde_json::Value::String(value) => Ok(vec![value.clone()]),
        serde_json::Value::Array(values) => values
            .iter()
            .map(|value| {
                value.as_str().map(str::to_string).ok_or_else(|| {
                    RuntimeError::InvalidBucketPolicy(format!(
                        "{field} array entries must be strings"
                    ))
                })
            })
            .collect(),
        _ => Err(RuntimeError::InvalidBucketPolicy(format!(
            "{field} must be a string or string array"
        ))),
    }
}