use crate::auth::AuthContext;
use crate::capabilities::MethodPath;
use crate::principal::Principal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct CallSite {
pub caller: Principal,
pub callee_method: MethodPath,
}
impl CallSite {
pub fn new(caller: Principal, callee_method: MethodPath) -> Self {
Self {
caller,
callee_method,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForwardDerivation {
pub keep_verified_user: bool,
pub keep_roles: bool,
pub keep_capabilities: bool,
pub keep_metadata: bool,
}
impl ForwardDerivation {
pub const IDENTITY_ONLY: Self = Self {
keep_verified_user: true,
keep_roles: false,
keep_capabilities: false,
keep_metadata: false,
};
pub const PASS_THROUGH: Self = Self {
keep_verified_user: true,
keep_roles: true,
keep_capabilities: true,
keep_metadata: true,
};
pub const ANONYMOUS: Self = Self {
keep_verified_user: false,
keep_roles: false,
keep_capabilities: false,
keep_metadata: false,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ForwardPolicyName(&'static str);
impl ForwardPolicyName {
pub const fn new(name: &'static str) -> Self {
Self(name)
}
pub fn as_str(&self) -> &'static str {
self.0
}
}
impl std::fmt::Display for ForwardPolicyName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
pub trait ForwardPolicy: Send + Sync + 'static {
fn name(&self) -> ForwardPolicyName;
fn forward(&self, caller_ctx: &AuthContext, site: &CallSite) -> ForwardDerivation;
}
pub const IDENTITY_ONLY_NAME: ForwardPolicyName = ForwardPolicyName::new("identity_only");
pub const PASS_THROUGH_NAME: ForwardPolicyName = ForwardPolicyName::new("pass_through");
pub const ANONYMOUS_NAME: ForwardPolicyName = ForwardPolicyName::new("anonymous");
#[derive(Debug, Clone, Copy)]
pub struct IdentityOnly;
impl ForwardPolicy for IdentityOnly {
fn name(&self) -> ForwardPolicyName {
IDENTITY_ONLY_NAME
}
fn forward(&self, _ctx: &AuthContext, _site: &CallSite) -> ForwardDerivation {
ForwardDerivation::IDENTITY_ONLY
}
}
#[derive(Debug, Clone, Copy)]
pub struct PassThrough;
impl ForwardPolicy for PassThrough {
fn name(&self) -> ForwardPolicyName {
PASS_THROUGH_NAME
}
fn forward(&self, _ctx: &AuthContext, _site: &CallSite) -> ForwardDerivation {
ForwardDerivation::PASS_THROUGH
}
}
#[derive(Debug, Clone, Copy)]
pub struct Anonymous;
impl ForwardPolicy for Anonymous {
fn name(&self) -> ForwardPolicyName {
ANONYMOUS_NAME
}
fn forward(&self, _ctx: &AuthContext, _site: &CallSite) -> ForwardDerivation {
ForwardDerivation::ANONYMOUS
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_callsite() -> CallSite {
CallSite::new(
Principal::anonymous_sealed(),
MethodPath::try_new("solar.earth.luna.info").unwrap(),
)
}
fn sample_ctx() -> AuthContext {
AuthContext::new(
"alice".to_string(),
"sess-1".to_string(),
vec!["admin".to_string()],
serde_json::json!({"tenant_id": "acme"}),
)
}
#[test]
fn identity_only_returns_identity_only_constant() {
let policy = IdentityOnly;
assert_eq!(policy.name(), IDENTITY_ONLY_NAME);
assert_eq!(policy.name().as_str(), "identity_only");
let d = policy.forward(&sample_ctx(), &sample_callsite());
assert_eq!(d, ForwardDerivation::IDENTITY_ONLY);
assert!(d.keep_verified_user);
assert!(!d.keep_roles);
assert!(!d.keep_capabilities);
assert!(!d.keep_metadata);
}
#[test]
fn pass_through_returns_pass_through_constant() {
let policy = PassThrough;
assert_eq!(policy.name(), PASS_THROUGH_NAME);
assert_eq!(policy.name().as_str(), "pass_through");
let d = policy.forward(&sample_ctx(), &sample_callsite());
assert_eq!(d, ForwardDerivation::PASS_THROUGH);
assert!(d.keep_verified_user);
assert!(d.keep_roles);
assert!(d.keep_capabilities);
assert!(d.keep_metadata);
}
#[test]
fn anonymous_returns_anonymous_constant() {
let policy = Anonymous;
assert_eq!(policy.name(), ANONYMOUS_NAME);
assert_eq!(policy.name().as_str(), "anonymous");
let d = policy.forward(&sample_ctx(), &sample_callsite());
assert_eq!(d, ForwardDerivation::ANONYMOUS);
assert!(!d.keep_verified_user);
assert!(!d.keep_roles);
assert!(!d.keep_capabilities);
assert!(!d.keep_metadata);
}
#[test]
fn three_constants_are_distinct() {
assert_ne!(ForwardDerivation::IDENTITY_ONLY, ForwardDerivation::PASS_THROUGH);
assert_ne!(ForwardDerivation::IDENTITY_ONLY, ForwardDerivation::ANONYMOUS);
assert_ne!(ForwardDerivation::PASS_THROUGH, ForwardDerivation::ANONYMOUS);
}
#[test]
fn policy_holdable_as_trait_object() {
use std::sync::Arc;
let policies: Vec<Arc<dyn ForwardPolicy>> = vec![
Arc::new(IdentityOnly),
Arc::new(PassThrough),
Arc::new(Anonymous),
];
let names: Vec<&'static str> = policies.iter().map(|p| p.name().as_str()).collect();
assert_eq!(names, vec!["identity_only", "pass_through", "anonymous"]);
}
#[test]
fn policy_name_display_uses_inner_string() {
assert_eq!(format!("{}", IDENTITY_ONLY_NAME), "identity_only");
}
}