1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Error, Result};
6
7pub const MAX_LABEL_LEN: usize = 256;
9pub const MAX_CAPABILITIES: usize = 64;
10
11pub(crate) fn valid_label(value: &str) -> bool {
12 !value.is_empty() && value.len() <= MAX_LABEL_LEN && !value.chars().any(char::is_control)
13}
14
15macro_rules! label {
16 ($name:ident, $doc:literal) => {
17 #[doc = $doc]
18 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19 #[serde(try_from = "String", into = "String")]
20 pub struct $name(String);
21
22 impl $name {
23 pub fn new(value: impl Into<String>) -> Result<Self> {
24 let value = value.into();
25 if !valid_label(&value) {
26 return Err(Error::InvalidInput);
27 }
28 Ok(Self(value))
29 }
30
31 pub fn as_str(&self) -> &str {
32 &self.0
33 }
34 }
35
36 impl TryFrom<String> for $name {
37 type Error = Error;
38 fn try_from(value: String) -> Result<Self> {
39 Self::new(value)
40 }
41 }
42
43 impl From<$name> for String {
44 fn from(value: $name) -> Self {
45 value.0
46 }
47 }
48 };
49}
50
51label!(Audience, "Exact logical recipient; independent of DNS and transport.");
52label!(Capability, "Exact application-defined permission; no implicit wildcards or hierarchy.");
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum Purpose {
58 Session,
59 Refresh,
60 Access,
61 WebSocket,
62 Internal
63}
64
65impl Purpose {
66 pub(crate) fn as_bytes(self) -> &'static [u8] {
67 match self {
68 Self::Session => b"session",
69 Self::Refresh => b"refresh",
70 Self::Access => b"access",
71 Self::WebSocket => b"websocket",
72 Self::Internal => b"internal"
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub struct TokenId(pub [u8; 16]);
80
81#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(deny_unknown_fields)]
84pub struct Claims {
85 pub subject: String,
86 pub audience: Audience,
87 pub purpose: Purpose,
88 pub capabilities: BTreeSet<Capability>,
89 pub issued_at: u64,
90 pub not_before: u64,
91 pub expires_at: u64
92}
93
94impl Claims {
95 pub(crate) fn check_shape(&self) -> Result<()> {
96 if !valid_label(&self.subject)
97 || self.capabilities.len() > MAX_CAPABILITIES
98 || self.issued_at > self.not_before
99 || self.not_before >= self.expires_at
100 {
101 return Err(Error::InvalidInput);
102 }
103 Ok(())
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct Principal {
111 pub(crate) realm: String,
112 pub(crate) issuer: String,
113 pub(crate) jti: TokenId,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub(crate) session: Option<crate::fleet::SessionBinding>,
116 pub(crate) claims: Claims
117}
118
119impl Principal {
120 pub fn session_id(&self) -> Option<crate::SessionId> {
121 self.session.as_ref().map(|session| session.id)
122 }
123 pub fn realm(&self) -> &str {
124 &self.realm
125 }
126 pub fn issuer(&self) -> &str {
127 &self.issuer
128 }
129 pub fn jti(&self) -> TokenId {
130 self.jti
131 }
132 pub fn subject(&self) -> &str {
133 &self.claims.subject
134 }
135 pub fn claims(&self) -> &Claims {
136 &self.claims
137 }
138 pub fn has_capability(
139 &self,
140 capability: &Capability
141 ) -> bool {
142 self.claims.capabilities.contains(capability)
143 }
144}
145
146#[derive(Clone, Debug)]
148pub struct Validation {
149 pub audience: Audience,
150 pub purpose: Purpose,
151 pub required_capabilities: BTreeSet<Capability>
152}
153
154pub trait ValidationHook {
160 fn check(
161 &self,
162 principal: &Principal,
163 now: u64
164 ) -> Result<()>;
165
166 fn check_lapsed(
169 &self,
170 _: &Principal,
171 _: u64
172 ) -> Result<()> {
173 Err(Error::Expired)
174 }
175}
176
177pub struct AllowReusable;
179
180impl ValidationHook for AllowReusable {
181 fn check(
182 &self,
183 _: &Principal,
184 _: u64
185 ) -> Result<()> {
186 Ok(())
187 }
188}