use std::sync::Arc;
use ed25519_dalek::VerifyingKey;
use crate::audit::{AuditSink, NoopAuditSink};
use crate::chain::{AuthorizedAction, BatchAuthorizeResult, Clock, DyoloChain, SystemClock};
use crate::error::KyaError;
use crate::intent::{IntentHash, MerkleProof};
use crate::policy::PolicySet;
use crate::registry::{MemoryNonceStore, MemoryRevocationStore, NonceStore, RevocationStore};
pub struct KyaContext {
pub revocation: Arc<dyn RevocationStore>,
pub nonces: Arc<dyn NonceStore>,
pub clock: Arc<dyn Clock + Send + Sync>,
pub policy: Option<PolicySet>,
pub audit: Arc<dyn AuditSink>,
pub namespace: Option<String>,
}
impl KyaContext {
pub fn builder() -> KyaContextBuilder {
KyaContextBuilder::default()
}
pub fn authorize(
&self,
chain: &DyoloChain,
agent_pk: &VerifyingKey,
intent: &IntentHash,
proof: &MerkleProof,
) -> Result<AuthorizedAction, KyaError> {
chain.authorize_with_options(
agent_pk,
intent,
proof,
self.clock.as_ref(),
self.revocation.as_ref(),
self.nonces.as_ref(),
self.policy.as_ref(),
self.audit.as_ref(),
)
}
pub fn authorize_batch(
&self,
chain: &DyoloChain,
agent_pk: &VerifyingKey,
intents: &[(IntentHash, MerkleProof)],
) -> BatchAuthorizeResult {
chain.authorize_batch(
agent_pk,
intents,
self.clock.as_ref(),
self.revocation.as_ref(),
self.nonces.as_ref(),
)
}
pub fn health_check(&self) -> Result<(), KyaError> {
self.revocation
.health_check()
.map_err(|e| KyaError::StorageUnhealthy(format!("revocation: {e}")))?;
self.nonces
.health_check()
.map_err(|e| KyaError::StorageUnhealthy(format!("nonces: {e}")))?;
Ok(())
}
}
#[cfg(feature = "async")]
pub struct AsyncKyaContext {
pub revocation: Arc<dyn crate::registry::r#async::AsyncRevocationStore>,
pub nonces: Arc<dyn crate::registry::r#async::AsyncNonceStore>,
pub clock: Arc<dyn Clock + Send + Sync>,
pub policy: Option<PolicySet>,
pub audit: Arc<dyn AuditSink>,
pub namespace: Option<String>,
}
#[cfg(feature = "async")]
impl AsyncKyaContext {
pub fn builder() -> AsyncKyaContextBuilder {
AsyncKyaContextBuilder::default()
}
pub async fn authorize(
&self,
chain: &DyoloChain,
agent_pk: &VerifyingKey,
intent: &IntentHash,
proof: &MerkleProof,
) -> Result<AuthorizedAction, KyaError> {
chain
.authorize_async_with_options(
agent_pk,
intent,
proof,
self.clock.as_ref(),
self.revocation.as_ref(),
self.nonces.as_ref(),
self.policy.as_ref(),
self.audit.as_ref(),
)
.await
}
pub async fn authorize_batch(
&self,
chain: &DyoloChain,
agent_pk: &VerifyingKey,
intents: &[(IntentHash, MerkleProof)],
) -> BatchAuthorizeResult {
chain
.authorize_batch_async(
agent_pk,
intents,
self.clock.as_ref(),
self.revocation.as_ref(),
self.nonces.as_ref(),
)
.await
}
pub async fn health_check(&self) -> Result<(), KyaError> {
self.revocation
.health_check()
.await
.map_err(|e| KyaError::StorageUnhealthy(format!("revocation: {e}")))?;
self.nonces
.health_check()
.await
.map_err(|e| KyaError::StorageUnhealthy(format!("nonces: {e}")))?;
Ok(())
}
}
#[derive(Default)]
pub struct KyaContextBuilder {
revocation: Option<Arc<dyn RevocationStore>>,
nonces: Option<Arc<dyn NonceStore>>,
clock: Option<Arc<dyn Clock + Send + Sync>>,
policy: Option<PolicySet>,
audit: Option<Arc<dyn AuditSink>>,
namespace: Option<String>,
}
impl KyaContextBuilder {
pub fn revocation(mut self, store: impl RevocationStore + 'static) -> Self {
self.revocation = Some(Arc::new(store));
self
}
pub fn nonces(mut self, store: impl NonceStore + 'static) -> Self {
self.nonces = Some(Arc::new(store));
self
}
pub fn clock(mut self, clock: impl Clock + Send + Sync + 'static) -> Self {
self.clock = Some(Arc::new(clock));
self
}
pub fn policy(mut self, policy: PolicySet) -> Self {
self.policy = Some(policy);
self
}
pub fn audit(mut self, sink: impl AuditSink + 'static) -> Self {
self.audit = Some(Arc::new(sink));
self
}
pub fn namespace(mut self, ns: impl Into<String>) -> Self {
self.namespace = Some(ns.into());
self
}
pub fn build(self) -> KyaContext {
KyaContext {
revocation: self
.revocation
.unwrap_or_else(|| Arc::new(MemoryRevocationStore::new())),
nonces: self
.nonces
.unwrap_or_else(|| Arc::new(MemoryNonceStore::new())),
clock: self.clock.unwrap_or_else(|| Arc::new(SystemClock)),
policy: self.policy,
audit: self.audit.unwrap_or_else(|| Arc::new(NoopAuditSink)),
namespace: self.namespace,
}
}
}
#[cfg(feature = "async")]
#[derive(Default)]
pub struct AsyncKyaContextBuilder {
revocation: Option<Arc<dyn crate::registry::r#async::AsyncRevocationStore>>,
nonces: Option<Arc<dyn crate::registry::r#async::AsyncNonceStore>>,
clock: Option<Arc<dyn Clock + Send + Sync>>,
policy: Option<PolicySet>,
audit: Option<Arc<dyn AuditSink>>,
namespace: Option<String>,
}
#[cfg(feature = "async")]
impl AsyncKyaContextBuilder {
pub fn revocation(
mut self,
store: impl crate::registry::r#async::AsyncRevocationStore + 'static,
) -> Self {
self.revocation = Some(Arc::new(store));
self
}
pub fn nonces(
mut self,
store: impl crate::registry::r#async::AsyncNonceStore + 'static,
) -> Self {
self.nonces = Some(Arc::new(store));
self
}
pub fn clock(mut self, clock: impl Clock + Send + Sync + 'static) -> Self {
self.clock = Some(Arc::new(clock));
self
}
pub fn policy(mut self, policy: PolicySet) -> Self {
self.policy = Some(policy);
self
}
pub fn audit(mut self, sink: impl AuditSink + 'static) -> Self {
self.audit = Some(Arc::new(sink));
self
}
pub fn namespace(mut self, ns: impl Into<String>) -> Self {
self.namespace = Some(ns.into());
self
}
pub fn build(self) -> AsyncKyaContext {
use crate::registry::r#async::{SyncNonceAdapter, SyncRevocationAdapter};
AsyncKyaContext {
revocation: self.revocation.unwrap_or_else(|| {
Arc::new(SyncRevocationAdapter(
Arc::new(MemoryRevocationStore::new()),
))
}),
nonces: self
.nonces
.unwrap_or_else(|| Arc::new(SyncNonceAdapter(Arc::new(MemoryNonceStore::new())))),
clock: self.clock.unwrap_or_else(|| Arc::new(SystemClock)),
policy: self.policy,
audit: self.audit.unwrap_or_else(|| Arc::new(NoopAuditSink)),
namespace: self.namespace,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(deprecated)]
use crate::{
cert::CertBuilder,
identity::DyoloIdentity,
intent::{intent_hash, IntentTree},
};
#[test]
#[allow(deprecated)]
fn context_builder_defaults_and_authorizes() {
let ctx = KyaContext::builder().build();
let human = DyoloIdentity::generate();
let agent = DyoloIdentity::generate();
let trade = intent_hash("trade.equity", b"");
let tree = IntentTree::build(vec![trade]).unwrap();
let root = tree.root();
let now = SystemClock.unix_now();
let cert =
CertBuilder::new(agent.verifying_key(), root, now, now + 86400 * 365).sign(&human);
let mut chain = DyoloChain::new(human.verifying_key(), root).with_drift_tolerance(9999999);
chain.push(cert);
let proof = tree.prove(&trade).unwrap();
let result = ctx.authorize(&chain, &agent.verifying_key(), &trade, &proof);
assert!(
result.is_ok(),
"context builder authorization failed: {:?}",
result.err()
);
}
#[test]
fn sync_context_health_check_returns_ok() {
let ctx = KyaContext::builder().build();
assert!(ctx.health_check().is_ok());
}
}