use std::sync::Arc;
use crate::proto::api::{
Capabilities, ChallengeResponse, CreateVaultRequest, Limits, ProofOfWork, feature,
};
use crate::server_core::auth::Caller;
use crate::server_core::error::CoreError;
pub trait Admission: Send + Sync {
fn proof_of_work(&self) -> Option<u8> {
None
}
fn challenge(&self, _now: i64) -> Result<ChallengeResponse, CoreError> {
Err(CoreError::no_endpoint())
}
fn admit_creation(
&self,
_request: &CreateVaultRequest,
_now: i64,
) -> Result<Option<Admitted>, CoreError> {
Ok(None)
}
fn admit_request(&self, _caller: &Caller, _now: i64) -> Result<(), CoreError> {
Ok(())
}
fn features(&self) -> Vec<&'static str> {
Vec::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Admitted {
pub challenge_id: [u8; 16],
pub expires_at: i64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AllowAll;
impl Admission for AllowAll {}
#[derive(Clone)]
pub struct Policy {
pub admission: Arc<dyn Admission>,
pub idle_expiry_days: Option<u32>,
pub limits: Limits,
pub server: Option<String>,
}
impl Default for Policy {
fn default() -> Policy {
Policy {
admission: Arc::new(AllowAll),
idle_expiry_days: None,
limits: Limits::default(),
server: None,
}
}
}
impl std::fmt::Debug for Policy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Policy")
.field("proof_of_work", &self.admission.proof_of_work())
.field("idle_expiry_days", &self.idle_expiry_days)
.field("limits", &self.limits)
.field("server", &self.server)
.finish()
}
}
impl Policy {
pub fn idle_secs(&self) -> Option<i64> {
self.idle_expiry_days
.filter(|days| *days > 0)
.map(|days| i64::from(days) * 86_400)
}
pub fn capabilities(&self) -> Capabilities {
let mut features = vec![feature::TOKEN_REPORT];
features.extend(self.admission.features());
Capabilities::new(
self.admission.proof_of_work().map(ProofOfWork::new),
self.idle_expiry_days.filter(|days| *days > 0),
self.limits,
)
.with_features(features)
.with_server(self.server.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_policy_asks_for_nothing() {
let policy = Policy::default();
assert_eq!(policy.idle_secs(), None);
let caps = policy.capabilities();
assert_eq!(caps.proof_of_work, None);
assert_eq!(caps.idle_expiry_days, None);
assert_eq!(caps.limits, Limits::default());
let json = serde_json::to_value(&caps).unwrap();
assert!(json["proof_of_work"].is_null() && json["idle_expiry_days"].is_null());
assert_eq!(json["protocols"], serde_json::json!(["1"]));
assert_eq!(json["features"], serde_json::json!(["token_report"]));
assert!(json.get("server").is_none(), "no version unless asked");
}
#[test]
fn the_version_is_advertised_only_when_asked() {
let policy = Policy {
server: Some("gv-server/9.9.9".into()),
..Policy::default()
};
let caps = policy.capabilities();
assert_eq!(caps.server.as_deref(), Some("gv-server/9.9.9"));
let default = Policy::default().capabilities();
assert_eq!(caps.limits, default.limits);
assert_eq!(caps.features, default.features);
}
#[test]
fn zero_days_is_never() {
let policy = Policy {
idle_expiry_days: Some(0),
..Policy::default()
};
assert_eq!(policy.idle_secs(), None);
assert_eq!(policy.capabilities().idle_expiry_days, None);
let policy = Policy {
idle_expiry_days: Some(30),
..Policy::default()
};
assert_eq!(policy.idle_secs(), Some(30 * 86_400));
assert_eq!(policy.capabilities().idle_expiry_days, Some(30));
}
}