use chrono::Utc;
use std::collections::HashMap;
use std::time::Duration;
use tenuo::{
approval::{compute_request_hash, ApprovalPayload, SignedApproval},
approval_gate::{encode_approval_gate_map, ApprovalGateMap, ToolApprovalGate},
constraints::{ConstraintSet, Pattern},
crypto::SigningKey,
planes::Authorizer,
warrant::Warrant,
};
#[test]
fn test_single_approval_succeeds() {
let root_key = SigningKey::generate();
let approver = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("action".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("action", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![approver.public_key()])
.min_approvals(1)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"action",
&args,
Some(&root_key.public_key()),
);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce,
external_id: "approver@example.com".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &approver);
let sig = warrant.sign(&root_key, "action", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "action", &args, Some(&sig), &[approval]);
assert!(
result.is_ok(),
"Single valid approval should succeed: {:?}",
result.err()
);
}
#[test]
fn test_two_of_three_approvals_succeeds() {
let root_key = SigningKey::generate();
let approver_1 = SigningKey::generate();
let approver_2 = SigningKey::generate();
let approver_3 = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert(
"critical_action".to_string(),
ToolApprovalGate::whole_tool(),
);
let warrant = Warrant::builder()
.capability("critical_action", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![
approver_1.public_key(),
approver_2.public_key(),
approver_3.public_key(),
])
.min_approvals(2)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"critical_action",
&args,
Some(&root_key.public_key()),
);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce_1: [u8; 16] = rand::random();
let payload_1 = ApprovalPayload {
version: 1,
request_hash,
nonce: nonce_1,
external_id: "approver1@example.com".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval_1 = SignedApproval::create(payload_1, &approver_1);
let nonce_2: [u8; 16] = rand::random();
let payload_2 = ApprovalPayload {
version: 1,
request_hash,
nonce: nonce_2,
external_id: "approver2@example.com".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval_2 = SignedApproval::create(payload_2, &approver_2);
let sig = warrant.sign(&root_key, "critical_action", &args).unwrap();
let result = authorizer.authorize_one(
&warrant,
"critical_action",
&args,
Some(&sig),
&[approval_1, approval_2],
);
assert!(
result.is_ok(),
"2-of-3 with 2 valid approvals should succeed: {:?}",
result.err()
);
}
#[test]
fn test_no_multisig_requirement_succeeds_without_approvals() {
let root_key = SigningKey::generate();
let warrant = Warrant::builder()
.capability("simple_action", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.holder(root_key.public_key())
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let sig = warrant.sign(&root_key, "simple_action", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "simple_action", &args, Some(&sig), &[]);
assert!(
result.is_ok(),
"Non-multisig warrant should succeed without approvals"
);
}
#[test]
fn test_duplicate_approvals_rejected() {
let root_key = SigningKey::generate();
let approver_1 = SigningKey::generate();
let approver_2 = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("critical_op".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("critical_op", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![approver_1.public_key(), approver_2.public_key()])
.min_approvals(2)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(&warrant.id().to_string(), "critical_op", &args, None);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce,
external_id: "approver_1".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &approver_1);
let approvals = vec![approval.clone(), approval.clone()];
let sig = warrant.sign(&root_key, "critical_op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "critical_op", &args, Some(&sig), &approvals);
assert!(result.is_err(), "Duplicate approvals should be rejected");
}
#[test]
fn test_insufficient_approvals_rejected() {
let root_key = SigningKey::generate();
let approver_1 = SigningKey::generate();
let approver_2 = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("critical_op".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("critical_op", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![approver_1.public_key(), approver_2.public_key()])
.min_approvals(2)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(&warrant.id().to_string(), "critical_op", &args, None);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce,
external_id: "approver_1".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &approver_1);
let sig = warrant.sign(&root_key, "critical_op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "critical_op", &args, Some(&sig), &[approval]);
assert!(result.is_err(), "Insufficient approvals should be rejected");
}
#[test]
fn test_unauthorized_approver_rejected() {
let root_key = SigningKey::generate();
let authorized_approver = SigningKey::generate();
let random_attacker = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("critical_op".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("critical_op", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![authorized_approver.public_key()])
.min_approvals(1)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(&warrant.id().to_string(), "critical_op", &args, None);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce,
external_id: "attacker".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &random_attacker);
let sig = warrant.sign(&root_key, "critical_op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "critical_op", &args, Some(&sig), &[approval]);
assert!(result.is_err(), "Unauthorized approver should be rejected");
}
#[test]
fn test_mismatched_request_hash_rejected() {
let root_key = SigningKey::generate();
let approver = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("critical_op".to_string(), ToolApprovalGate::whole_tool());
gates.insert("other_op".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("critical_op", ConstraintSet::new())
.capability("other_op", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![approver.public_key()])
.min_approvals(1)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let other_hash = compute_request_hash(&warrant.id().to_string(), "other_op", &args, None);
let now = Utc::now();
let expires = now + chrono::Duration::hours(1);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash: other_hash, nonce,
external_id: "approver".to_string(),
approved_at: now.timestamp() as u64,
expires_at: expires.timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &approver);
let sig = warrant.sign(&root_key, "critical_op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "critical_op", &args, Some(&sig), &[approval]);
assert!(
result.is_err(),
"Mismatched request hash should be rejected"
);
}
#[test]
fn test_expired_approval_rejected() {
let root_key = SigningKey::generate();
let approver = SigningKey::generate();
let mut gates = ApprovalGateMap::new();
gates.insert("critical_op".to_string(), ToolApprovalGate::whole_tool());
let warrant = Warrant::builder()
.capability("critical_op", ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(vec![approver.public_key()])
.min_approvals(1)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(&root_key)
.unwrap();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(&warrant.id().to_string(), "critical_op", &args, None);
let now = Utc::now();
let expired_time = now - chrono::Duration::hours(1);
let approved_at = now - chrono::Duration::hours(2);
let nonce: [u8; 16] = rand::random();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce,
external_id: "approver".to_string(),
approved_at: approved_at.timestamp() as u64,
expires_at: expired_time.timestamp() as u64, extensions: None,
};
let approval = SignedApproval::create(payload, &approver);
let sig = warrant.sign(&root_key, "critical_op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "critical_op", &args, Some(&sig), &[approval]);
assert!(result.is_err(), "Expired approval should be rejected");
}
fn make_multisig_warrant(
root_key: &SigningKey,
tool: &str,
approver_keys: &[&SigningKey],
min_approvals: u32,
) -> Warrant {
let approver_pks: Vec<_> = approver_keys.iter().map(|k| k.public_key()).collect();
let mut gates = ApprovalGateMap::new();
gates.insert(tool.to_string(), ToolApprovalGate::whole_tool());
Warrant::builder()
.capability(tool, ConstraintSet::new())
.ttl(Duration::from_secs(3600))
.required_approvers(approver_pks)
.min_approvals(min_approvals)
.holder(root_key.public_key())
.extension(
"tenuo.approval_gates",
encode_approval_gate_map(&gates).unwrap(),
)
.build(root_key)
.unwrap()
}
fn make_approval(
root_key: &SigningKey,
approver: &SigningKey,
warrant: &Warrant,
tool: &str,
external_id: &str,
) -> SignedApproval {
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
tool,
&args,
Some(&root_key.public_key()),
);
let now = Utc::now();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce: rand::random(),
external_id: external_id.to_string(),
approved_at: now.timestamp() as u64,
expires_at: (now + chrono::Duration::hours(1)).timestamp() as u64,
extensions: None,
};
SignedApproval::create(payload, approver)
}
#[test]
fn test_three_of_five_exact_threshold() {
let root = SigningKey::generate();
let approvers: Vec<_> = (0..5).map(|_| SigningKey::generate()).collect();
let approver_refs: Vec<&SigningKey> = approvers.iter().collect();
let warrant = make_multisig_warrant(&root, "deploy", &approver_refs, 3);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let a0 = make_approval(&root, &approvers[0], &warrant, "deploy", "alice");
let a1 = make_approval(&root, &approvers[1], &warrant, "deploy", "bob");
let a2 = make_approval(&root, &approvers[2], &warrant, "deploy", "carol");
let args = HashMap::new();
let sig = warrant.sign(&root, "deploy", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "deploy", &args, Some(&sig), &[a0, a1, a2]);
assert!(
result.is_ok(),
"3-of-5 with 3 valid should succeed: {:?}",
result.err()
);
}
#[test]
fn test_three_of_five_all_approve() {
let root = SigningKey::generate();
let approvers: Vec<_> = (0..5).map(|_| SigningKey::generate()).collect();
let approver_refs: Vec<&SigningKey> = approvers.iter().collect();
let warrant = make_multisig_warrant(&root, "deploy", &approver_refs, 3);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let all: Vec<_> = approvers
.iter()
.enumerate()
.map(|(i, k)| make_approval(&root, k, &warrant, "deploy", &format!("approver-{i}")))
.collect();
let args = HashMap::new();
let sig = warrant.sign(&root, "deploy", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "deploy", &args, Some(&sig), &all);
assert!(
result.is_ok(),
"3-of-5 with all 5 should succeed: {:?}",
result.err()
);
}
#[test]
fn test_three_of_five_insufficient() {
let root = SigningKey::generate();
let approvers: Vec<_> = (0..5).map(|_| SigningKey::generate()).collect();
let approver_refs: Vec<&SigningKey> = approvers.iter().collect();
let warrant = make_multisig_warrant(&root, "deploy", &approver_refs, 3);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let a0 = make_approval(&root, &approvers[0], &warrant, "deploy", "alice");
let a1 = make_approval(&root, &approvers[1], &warrant, "deploy", "bob");
let args = HashMap::new();
let sig = warrant.sign(&root, "deploy", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "deploy", &args, Some(&sig), &[a0, a1]);
assert!(result.is_err(), "3-of-5 with only 2 valid should fail");
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("insufficient approvals"),
"Error should say 'insufficient approvals', got: {err_msg}"
);
assert!(
err_msg.contains("required 3") && err_msg.contains("received 2"),
"Error should include counts, got: {err_msg}"
);
}
#[test]
fn test_two_of_three_with_mixed_invalid() {
let root = SigningKey::generate();
let a1 = SigningKey::generate();
let a2 = SigningKey::generate();
let a3 = SigningKey::generate();
let outsider = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&a1, &a2, &a3], 2);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"op",
&args,
Some(&root.public_key()),
);
let now = Utc::now();
let valid = make_approval(&root, &a1, &warrant, "op", "alice");
let expired_payload = ApprovalPayload {
version: 1,
request_hash,
nonce: rand::random(),
external_id: "bob-expired".to_string(),
approved_at: (now - chrono::Duration::hours(3)).timestamp() as u64,
expires_at: (now - chrono::Duration::hours(1)).timestamp() as u64,
extensions: None,
};
let expired = SignedApproval::create(expired_payload, &a2);
let untrusted = make_approval(&root, &outsider, &warrant, "op", "outsider");
let valid2 = make_approval(&root, &a3, &warrant, "op", "carol");
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(
&warrant,
"op",
&args,
Some(&sig),
&[valid, expired, untrusted, valid2],
);
assert!(
result.is_ok(),
"Should succeed with 2 valid despite invalid ones: {:?}",
result.err()
);
}
#[test]
fn test_two_of_three_all_invalid_shows_summary() {
let root = SigningKey::generate();
let a1 = SigningKey::generate();
let a2 = SigningKey::generate();
let a3 = SigningKey::generate();
let outsider = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&a1, &a2, &a3], 2);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"op",
&args,
Some(&root.public_key()),
);
let now = Utc::now();
let expired_payload = ApprovalPayload {
version: 1,
request_hash,
nonce: rand::random(),
external_id: "alice-expired".to_string(),
approved_at: (now - chrono::Duration::hours(3)).timestamp() as u64,
expires_at: (now - chrono::Duration::hours(1)).timestamp() as u64,
extensions: None,
};
let expired = SignedApproval::create(expired_payload, &a1);
let untrusted = make_approval(&root, &outsider, &warrant, "op", "rogue");
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[expired, untrusted]);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("insufficient approvals"),
"m-of-n error should say 'insufficient approvals', got: {err_msg}"
);
assert!(
err_msg.contains("rejected"),
"m-of-n error should include rejection details, got: {err_msg}"
);
}
#[test]
fn test_one_of_one_untrusted_key_specific_error() {
let root = SigningKey::generate();
let authorized = SigningKey::generate();
let rogue = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&authorized], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let rogue_approval = make_approval(&root, &rogue, &warrant, "op", "rogue");
let args = HashMap::new();
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[rogue_approval]);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("approver not in trusted set"),
"1-of-1 untrusted should give specific reason, got: {err_msg}"
);
}
#[test]
fn test_one_of_one_expired_specific_error() {
let root = SigningKey::generate();
let approver = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&approver], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"op",
&args,
Some(&root.public_key()),
);
let now = Utc::now();
let expired_payload = ApprovalPayload {
version: 1,
request_hash,
nonce: rand::random(),
external_id: "slow".to_string(),
approved_at: (now - chrono::Duration::hours(3)).timestamp() as u64,
expires_at: (now - chrono::Duration::hours(1)).timestamp() as u64,
extensions: None,
};
let expired = SignedApproval::create(expired_payload, &approver);
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[expired]);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("expired"),
"1-of-1 expired should give specific reason, got: {err_msg}"
);
}
#[test]
fn test_one_of_one_hash_mismatch_specific_error() {
let root = SigningKey::generate();
let approver = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&approver], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let wrong_hash = compute_request_hash(
&warrant.id().to_string(),
"different_op",
&args,
Some(&root.public_key()),
);
let now = Utc::now();
let wrong_payload = ApprovalPayload {
version: 1,
request_hash: wrong_hash,
nonce: rand::random(),
external_id: "approver".to_string(),
approved_at: now.timestamp() as u64,
expires_at: (now + chrono::Duration::hours(1)).timestamp() as u64,
extensions: None,
};
let wrong_approval = SignedApproval::create(wrong_payload, &approver);
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[wrong_approval]);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("request hash mismatch"),
"1-of-1 hash mismatch should give specific reason, got: {err_msg}"
);
}
#[test]
fn test_m_of_n_duplicate_counted_once() {
let root = SigningKey::generate();
let a1 = SigningKey::generate();
let a2 = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&a1, &a2], 2);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let approval_1a = make_approval(&root, &a1, &warrant, "op", "alice-attempt-1");
let approval_1b = make_approval(&root, &a1, &warrant, "op", "alice-attempt-2");
let args = HashMap::new();
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(
&warrant,
"op",
&args,
Some(&sig),
&[approval_1a, approval_1b],
);
assert!(
result.is_err(),
"Duplicate approver should not count twice for 2-of-2"
);
}
#[test]
fn test_m_of_n_no_approvals_provided() {
let root = SigningKey::generate();
let a1 = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&a1], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[]);
assert!(result.is_err(), "No approvals should fail m-of-n");
}
#[test]
fn test_dos_protection_too_many_approvals() {
let root = SigningKey::generate();
let a1 = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&a1], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let a = make_approval(&root, &a1, &warrant, "op", "alice-1");
let b = make_approval(&root, &a1, &warrant, "op", "alice-2");
let c = make_approval(&root, &a1, &warrant, "op", "alice-3");
let args = HashMap::new();
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[a, b, c]);
assert!(result.is_err(), "Too many approvals should be rejected");
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("too many approvals"),
"Should mention DoS protection, got: {err_msg}"
);
}
#[test]
fn test_clock_tolerance_allows_near_expiry() {
let root = SigningKey::generate();
let approver = SigningKey::generate();
let warrant = make_multisig_warrant(&root, "op", &[&approver], 1);
let authorizer = Authorizer::new().with_trusted_root(root.public_key());
let args = HashMap::new();
let request_hash = compute_request_hash(
&warrant.id().to_string(),
"op",
&args,
Some(&root.public_key()),
);
let now = Utc::now();
let payload = ApprovalPayload {
version: 1,
request_hash,
nonce: rand::random(),
external_id: "approver".to_string(),
approved_at: (now - chrono::Duration::minutes(5)).timestamp() as u64,
expires_at: (now - chrono::Duration::seconds(20)).timestamp() as u64,
extensions: None,
};
let approval = SignedApproval::create(payload, &approver);
let sig = warrant.sign(&root, "op", &args).unwrap();
let result = authorizer.authorize_one(&warrant, "op", &args, Some(&sig), &[approval]);
assert!(
result.is_ok(),
"Approval within clock tolerance should succeed: {:?}",
result.err()
);
}
#[test]
fn test_suffix_pattern_cannot_widen() {
let parent = Pattern::new("*-safe").unwrap();
let child = Pattern::new("*").unwrap();
let result = parent.validate_attenuation(&child);
assert!(
result.is_err(),
"Suffix pattern should not allow wildcard-only child"
);
}
#[test]
fn test_infix_pattern_requires_exact() {
let parent = Pattern::new("img-*.png").unwrap();
let child = Pattern::new("img-*").unwrap();
let result = parent.validate_attenuation(&child);
assert!(
result.is_err(),
"Infix pattern should not allow suffix removal"
);
}
#[test]
fn test_prefix_pattern_valid_attenuation() {
let parent = Pattern::new("staging-*").unwrap();
let child1 = Pattern::new("staging-web-*").unwrap();
assert!(parent.validate_attenuation(&child1).is_ok());
let child2 = Pattern::new("staging-web").unwrap();
assert!(parent.validate_attenuation(&child2).is_ok());
let child3 = Pattern::new("prod-*").unwrap();
assert!(parent.validate_attenuation(&child3).is_err());
}
#[test]
fn test_suffix_pattern_valid_attenuation() {
let parent = Pattern::new("*-safe").unwrap();
let child1 = Pattern::new("*-extra-safe").unwrap();
assert!(parent.validate_attenuation(&child1).is_ok());
let child2 = Pattern::new("image-safe").unwrap();
assert!(parent.validate_attenuation(&child2).is_ok());
let child3 = Pattern::new("*-unsafe").unwrap();
assert!(parent.validate_attenuation(&child3).is_err());
}
#[test]
fn test_expired_parent_invalidates_chain() {
let root_key = SigningKey::generate();
let delegator = SigningKey::generate();
let worker = SigningKey::generate();
let authorizer = Authorizer::new().with_trusted_root(root_key.public_key());
let root = Warrant::builder()
.capability("read", ConstraintSet::new())
.holder(delegator.public_key())
.ttl(Duration::from_secs(1))
.build(&root_key)
.unwrap();
let child = root
.attenuate()
.capability("read", ConstraintSet::new())
.holder(worker.public_key())
.ttl(Duration::from_secs(1))
.build(&delegator)
.unwrap();
let args = HashMap::new();
let pop = child.sign(&worker, "read", &args).unwrap();
assert!(authorizer
.check_chain(
&[root.clone(), child.clone()],
"read",
&args,
Some(&pop),
&[]
)
.is_ok());
std::thread::sleep(Duration::from_millis(1500));
let pop2 = child.sign(&worker, "read", &args).unwrap();
let result = authorizer.check_chain(&[root, child], "read", &args, Some(&pop2), &[]);
assert!(result.is_err(), "expired chain must be rejected");
let err = result.unwrap_err().to_string();
assert!(
err.contains("expired") || err.contains("Expired"),
"error should mention expiration: {}",
err
);
}