ctoolbox/workspace/ipc/auth/
capability.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(
6 Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default,
7)]
8pub struct CapabilityToken(pub String);
9
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct CapabilitySet {
13 pub allowed: HashMap<ServiceName, Vec<MethodRule>>,
15 pub global_limits: Option<GlobalLimits>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub struct ServiceName(pub String);
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct MethodRule {
26 pub method: MethodSelector,
27 pub quotas: Option<QuotaSet>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub enum MethodSelector {
33 Exact(String),
35 Prefix(String),
37 Any,
39}
40
41impl MethodSelector {
42 pub fn matches(&self, service: &str, method: &str) -> bool {
44 let full_name = format!("{service}.{method}");
45 match self {
46 MethodSelector::Exact(s) => s == method || s == &full_name,
47 MethodSelector::Prefix(prefix) => {
48 method.starts_with(prefix) || full_name.starts_with(prefix)
49 }
50 MethodSelector::Any => true,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct QuotaSet {
58 pub bytes_per_sec: Option<u64>,
60 pub ops_per_sec: Option<u64>,
62 pub burst: Option<u64>,
64}
65
66impl QuotaSet {
67 pub fn effective_burst_bytes(&self) -> Option<u64> {
72 let Some(rate) = self.bytes_per_sec else {
73 return None;
74 };
75 Some(self.burst.unwrap_or(rate))
76 }
77
78 pub fn effective_burst_ops(&self) -> Option<u64> {
83 let Some(rate) = self.ops_per_sec else {
84 return None;
85 };
86 Some(self.burst.unwrap_or(rate))
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct GlobalLimits {
93 pub max_concurrent_requests: Option<u32>,
94 pub max_streams: Option<u32>,
95 pub max_blob_bytes: Option<u64>,
96}
97
98impl GlobalLimits {
99 pub fn is_empty(&self) -> bool {
101 self.max_concurrent_requests.is_none()
102 && self.max_streams.is_none()
103 && self.max_blob_bytes.is_none()
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, Default)]
109pub struct CapabilityBundle {
110 pub token: CapabilityToken,
112 pub capabilities: CapabilitySet,
114}
115
116pub trait TokenValidator: Send + Sync {
119 fn validate(
121 &self,
122 token: &CapabilityToken,
123 ) -> Result<CapabilitySet, anyhow::Error>;
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use anyhow::Result;
130
131 struct FakeTokenValidator;
133
134 impl TokenValidator for FakeTokenValidator {
135 fn validate(
136 &self,
137 token: &CapabilityToken,
138 ) -> Result<CapabilitySet, anyhow::Error> {
139 if token.0 == "ok" {
140 Ok(CapabilitySet::default())
141 } else {
142 Err(anyhow::anyhow!("invalid token"))
143 }
144 }
145 }
146
147 #[crate::ctb_test]
148 fn fake_validator_ok() -> Result<()> {
149 let v = FakeTokenValidator;
150 let set = v.validate(&CapabilityToken("ok".into()))?;
151 let _ = set; Ok(())
153 }
154
155 #[crate::ctb_test]
156 fn fake_validator_err() -> Result<()> {
157 let v = FakeTokenValidator;
158 let res = v.validate(&CapabilityToken("bad".into()));
159 assert!(res.is_err());
160 Ok(())
161 }
162}