use crate::approval::{ApprovalPayload, ApprovalRequest, SignedApproval};
use crate::crypto::SigningKey;
use chrono::{DateTime, Utc};
use std::fmt;
pub trait ApprovalProvider: Send + Sync {
fn approvals_for(
&self,
request: &ApprovalRequest,
) -> Result<Vec<SignedApproval>, ApprovalError>;
}
pub struct LocalApprovalSigner {
key: SigningKey,
external_id: String,
}
impl LocalApprovalSigner {
pub fn new(key: SigningKey, external_id: impl Into<String>) -> Self {
Self {
key,
external_id: external_id.into(),
}
}
}
impl ApprovalProvider for LocalApprovalSigner {
fn approvals_for(
&self,
request: &ApprovalRequest,
) -> Result<Vec<SignedApproval>, ApprovalError> {
if !request
.required_approvers
.iter()
.any(|key| key == &self.key.public_key())
{
return Err(ApprovalError::Unauthorized);
}
let expires_at = DateTime::<Utc>::from_timestamp(request.warrant_expires_at as i64, 0)
.ok_or(ApprovalError::Unavailable)?;
let nonce = *uuid::Uuid::new_v4().as_bytes();
let payload = ApprovalPayload::new(
request.request_hash,
nonce,
self.external_id.clone(),
Utc::now(),
expires_at,
);
Ok(vec![SignedApproval::create(payload, &self.key)])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApprovalError {
NoProvider,
Unavailable,
Unauthorized,
}
impl fmt::Display for ApprovalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoProvider => write!(f, "no approval provider is configured"),
Self::Unavailable => write!(f, "approval provider is unavailable"),
Self::Unauthorized => write!(f, "approver is not authorized for this request"),
}
}
}
impl std::error::Error for ApprovalError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::SigningKey;
use std::collections::HashMap;
#[test]
fn signs_a_core_request_and_rejects_the_wrong_key() {
let approver = SigningKey::generate();
let other = SigningKey::generate();
let request = ApprovalRequest::new(
"wrt_test",
"sensitive",
&HashMap::new(),
[7u8; 32],
vec![approver.public_key()],
1,
(Utc::now().timestamp() as u64) + 300,
);
let signed = LocalApprovalSigner::new(approver, "approver@local")
.approvals_for(&request)
.unwrap();
assert_eq!(signed.len(), 1);
assert!(signed[0].verify().is_ok());
assert_eq!(
LocalApprovalSigner::new(other, "intruder")
.approvals_for(&request)
.err()
.unwrap(),
ApprovalError::Unauthorized
);
}
}