use chrono::{Duration, TimeZone, Utc};
use delegated::{
AuditSink, DelegationTokenBuilder, Evaluator, InMemoryTrustState, OperationContext,
PinnedIssuerKeys, TrustStateAdmin, TrustStateError, TrustStateStore, VecAuditSink, envelope,
};
use ed25519_dalek::SigningKey;
use serde_json::{Value, json};
fn now() -> chrono::DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 7, 1, 12, 0, 0).single().unwrap()
}
fn fixture() -> (Value, SigningKey, PinnedIssuerKeys) {
let key = SigningKey::from_bytes(&[7; 32]);
let keys = PinnedIssuerKeys::new();
keys.insert("https://issuer.example", "issuer-1", key.verifying_key())
.unwrap();
let request = signed_request(
&key,
"https://issuer.example",
"issuer-1",
"nonce-0000000001",
now() - Duration::minutes(1),
now() + Duration::minutes(10),
);
(request, key, keys)
}
fn signed_request(
key: &SigningKey,
issuer: &str,
key_id: &str,
nonce: &str,
issued_at: chrono::DateTime<Utc>,
expires_at: chrono::DateTime<Utc>,
) -> Value {
let token = DelegationTokenBuilder::new()
.token_id("token-1")
.issuer(issuer)
.agent_id("agent:scheduler")
.delegator_id("user:alice")
.audience("calendar-api")
.allowed_action("calendar.create")
.allowed_resource("calendar:alice")
.max_delegation_depth(0)
.issued_at(issued_at)
.expires_at(expires_at)
.nonce(nonce)
.key_id(key_id)
.build_and_sign(key)
.unwrap();
serde_json::to_value(envelope(token, Some("request-1".into()))).unwrap()
}
fn operation() -> OperationContext {
OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:alice")
.with_delegation_depth(0)
}
fn encode(value: &Value) -> Vec<u8> {
serde_json::to_vec(value).unwrap()
}
#[test]
fn allows_a_trusted_issuer_for_the_actual_operation() {
let (request, _, keys) = fixture();
let state = InMemoryTrustState::new();
let (decision, event) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &operation(), now());
assert!(decision.allowed);
assert_eq!(event.action, "calendar.create");
assert_eq!(event.resource.as_deref(), Some("calendar:alice"));
}
#[test]
fn rejects_an_untrusted_self_selected_issuer_key() {
let (request, _, _) = fixture();
let attacker_keys = PinnedIssuerKeys::new();
let state = InMemoryTrustState::new();
let (decision, _) =
Evaluator::new(&attacker_keys, &state).evaluate(&encode(&request), &operation(), now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "verify_issuer");
}
#[test]
fn rejects_tampering_with_signed_claims() {
let (mut request, _, keys) = fixture();
request["delegation_token"]["agent_id"] = json!("agent:attacker");
let state = InMemoryTrustState::new();
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &operation(), now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "verify_issuer");
}
#[test]
fn host_operation_cannot_be_overridden_by_request_fields() {
let (mut request, _, keys) = fixture();
request["action"] = json!("calendar.create");
let state = InMemoryTrustState::new();
let operation = OperationContext::new("calendar-api", "calendar.delete")
.with_resource("calendar:alice")
.with_delegation_depth(0);
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &operation, now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "normalize_request");
}
#[test]
fn constrained_resource_and_depth_are_required() {
let (request, _, keys) = fixture();
let state = InMemoryTrustState::new();
let missing_resource =
OperationContext::new("calendar-api", "calendar.create").with_delegation_depth(0);
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &missing_resource, now());
assert!(!decision.allowed);
assert!(decision.reason.contains("resource is required"));
let missing_depth =
OperationContext::new("calendar-api", "calendar.create").with_resource("calendar:alice");
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &missing_depth, now());
assert!(!decision.allowed);
assert!(decision.reason.contains("depth is required"));
let wrong_resource = OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:bob")
.with_delegation_depth(0);
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &wrong_resource, now());
assert!(!decision.allowed);
assert!(decision.reason.contains("resource is not authorized"));
let excessive_depth = OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:alice")
.with_delegation_depth(1);
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &excessive_depth, now());
assert!(!decision.allowed);
assert!(decision.reason.contains("depth exceeds"));
}
#[test]
fn rejects_invalid_lifetime_windows() {
let key = SigningKey::from_bytes(&[7; 32]);
let keys = PinnedIssuerKeys::new();
keys.insert("https://issuer.example", "issuer-1", key.verifying_key())
.unwrap();
let state = InMemoryTrustState::new();
let evaluator = Evaluator::new(&keys, &state);
let cases = [
signed_request(
&key,
"https://issuer.example",
"issuer-1",
"nonce-expired-0001",
now() - Duration::hours(2),
now() - Duration::hours(1),
),
signed_request(
&key,
"https://issuer.example",
"issuer-1",
"nonce-future-00001",
now() + Duration::hours(1),
now() + Duration::hours(2),
),
signed_request(
&key,
"https://issuer.example",
"issuer-1",
"nonce-too-long-001",
now() - Duration::minutes(1),
now() + Duration::hours(25),
),
];
for request in cases {
let decision = evaluator.evaluate(&encode(&request), &operation(), now()).0;
assert!(!decision.allowed);
assert_eq!(decision.stage, "validate_lifetime");
}
}
#[test]
fn rejects_oversized_envelopes_before_parsing() {
let (_, _, keys) = fixture();
let state = InMemoryTrustState::new();
let input = vec![b' '; delegated::DEFAULT_MAX_ENVELOPE_BYTES + 1];
let (decision, event) = Evaluator::new(&keys, &state).evaluate(&input, &operation(), now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "normalize_request");
assert!(!event.claims_verified);
}
#[test]
fn nonce_consumption_is_atomic_under_concurrency() {
use std::sync::{Arc, Barrier};
let (request, _, keys) = fixture();
let request = Arc::new(encode(&request));
let keys = Arc::new(keys);
let state = Arc::new(InMemoryTrustState::new());
let barrier = Arc::new(Barrier::new(16));
let mut workers = Vec::new();
for _ in 0..16 {
let request = Arc::clone(&request);
let keys = Arc::clone(&keys);
let state = Arc::clone(&state);
let barrier = Arc::clone(&barrier);
workers.push(std::thread::spawn(move || {
barrier.wait();
Evaluator::new(keys.as_ref(), state.as_ref())
.evaluate(request.as_ref(), &operation(), now())
.0
.allowed
}));
}
let allowed = workers
.into_iter()
.map(|worker| worker.join().unwrap())
.filter(|allowed| *allowed)
.count();
assert_eq!(allowed, 1);
}
#[test]
fn unauthorized_attempt_does_not_burn_nonce() {
let (request, _, keys) = fixture();
let state = InMemoryTrustState::new();
let wrong = OperationContext::new("calendar-api", "calendar.delete")
.with_resource("calendar:alice")
.with_delegation_depth(0);
let evaluator = Evaluator::new(&keys, &state);
assert!(
!evaluator
.evaluate(&encode(&request), &wrong, now())
.0
.allowed
);
assert!(
evaluator
.evaluate(&encode(&request), &operation(), now())
.0
.allowed
);
let replay = evaluator.evaluate(&encode(&request), &operation(), now()).0;
assert!(!replay.allowed);
assert_eq!(replay.stage, "replay");
}
#[test]
fn revocation_and_backend_errors_fail_closed() {
let (request, _, keys) = fixture();
let state = InMemoryTrustState::new();
state
.revoke_token("https://issuer.example", "token-1")
.unwrap();
let (decision, _) =
Evaluator::new(&keys, &state).evaluate(&encode(&request), &operation(), now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "revocation");
struct FailedStore;
impl TrustStateStore for FailedStore {
fn is_token_revoked(&self, _: &str, _: &str) -> Result<bool, TrustStateError> {
Err(TrustStateError::new("offline"))
}
fn is_agent_denied(&self, _: &str, _: &str) -> Result<bool, TrustStateError> {
Err(TrustStateError::new("offline"))
}
fn consume_nonce(
&self,
_: &str,
_: &str,
_: chrono::DateTime<Utc>,
_: chrono::DateTime<Utc>,
) -> Result<bool, TrustStateError> {
Err(TrustStateError::new("offline"))
}
}
let (decision, _) =
Evaluator::new(&keys, &FailedStore).evaluate(&encode(&request), &operation(), now());
assert!(!decision.allowed);
assert_eq!(decision.stage, "trust_state");
}
#[test]
fn audit_is_mandatory_in_the_combined_api() {
let (request, _, keys) = fixture();
let state = InMemoryTrustState::new();
let sink = VecAuditSink::new();
let decision = Evaluator::new(&keys, &state)
.evaluate_and_audit(&encode(&request), &operation(), now(), &sink)
.unwrap();
assert!(decision.allowed);
assert_eq!(sink.events().unwrap().len(), 1);
struct FailedAudit;
impl AuditSink for FailedAudit {
fn write_event(&self, _: &delegated::AuditEvent) -> std::io::Result<()> {
Err(std::io::Error::other("offline"))
}
}
let (second_request, _, second_keys) = fixture();
let second_state = InMemoryTrustState::new();
let result = Evaluator::new(&second_keys, &second_state).evaluate_and_audit(
&encode(&second_request),
&operation(),
now(),
&FailedAudit,
);
assert!(result.is_err());
}
#[test]
fn mutable_state_keys_are_scoped_by_issuer() {
let key_a = SigningKey::from_bytes(&[7; 32]);
let key_b = SigningKey::from_bytes(&[8; 32]);
let keys = PinnedIssuerKeys::new();
keys.insert("issuer-a", "key", key_a.verifying_key())
.unwrap();
keys.insert("issuer-b", "key", key_b.verifying_key())
.unwrap();
let request_a = signed_request(
&key_a,
"issuer-a",
"key",
"shared-nonce-0001",
now() - Duration::minutes(1),
now() + Duration::minutes(10),
);
let request_b = signed_request(
&key_b,
"issuer-b",
"key",
"shared-nonce-0001",
now() - Duration::minutes(1),
now() + Duration::minutes(10),
);
let state = InMemoryTrustState::new();
state.revoke_token("issuer-a", "token-1").unwrap();
let evaluator = Evaluator::new(&keys, &state);
assert!(
!evaluator
.evaluate(&encode(&request_a), &operation(), now())
.0
.allowed
);
assert!(
evaluator
.evaluate(&encode(&request_b), &operation(), now())
.0
.allowed
);
}