1use std::{error::Error, fmt};
4
5use crate::{CanonicalValue, decode_canonical_value, encode_canonical_value};
6
7pub const PRINCIPAL_FACT_SET_VERSION_V1: u32 = 1;
9pub const MAX_PRINCIPAL_FACTS_V1: usize = 32;
11pub const MAX_PRINCIPAL_FACT_VALUES_V1: usize = 64;
13pub const MAX_PRINCIPAL_FACT_BYTES_V1: usize = 64 * 1024;
15const MAX_PRINCIPAL_FACT_NAME_BYTES_V1: usize = 256;
16const FACT_SET_MAGIC: &[u8] = b"RIFFDB-PRINCIPAL-FACTS\0";
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum PrincipalFactError {
21 InvalidName,
23 InvalidValue,
25 LimitExceeded,
27 Duplicate,
29 InvalidEncoding,
31}
32
33impl fmt::Display for PrincipalFactError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 formatter.write_str(match self {
36 Self::InvalidName => "principal fact name is invalid",
37 Self::InvalidValue => "principal fact value has an unsupported shape",
38 Self::LimitExceeded => "principal fact value exceeds a hard limit",
39 Self::Duplicate => "principal fact value contains a duplicate identity",
40 Self::InvalidEncoding => "principal fact canonical encoding failed",
41 })
42 }
43}
44
45impl Error for PrincipalFactError {}
46
47#[derive(Clone, Eq, PartialEq)]
49pub struct CapabilityPrincipalFactV1 {
50 name: String,
51 value: CanonicalValue,
52 encoded_value: crate::OwnedCapacitySliceV1<u8>,
53}
54impl crate::InspectOwnedCapacityV1 for CapabilityPrincipalFactV1 {
55 fn inspect_owned_capacity(
56 &self,
57 inspection: &mut crate::OwnedCapacityInspectionV1,
58 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
59 let Self {
60 name,
61 value,
62 encoded_value,
63 } = self;
64 inspection.visit(name)?;
65 inspection.visit(value)?;
66 inspection.visit(encoded_value)?;
67 Ok(())
68 }
69}
70
71impl CapabilityPrincipalFactV1 {
72 pub fn new(name: impl Into<String>, value: CanonicalValue) -> Result<Self, PrincipalFactError> {
74 let name = name.into();
75 validate_name(&name)?;
76 let value = canonicalize_value(value)?;
77 let encoded_value =
78 encode_canonical_value(&value).map_err(|_| PrincipalFactError::InvalidEncoding)?;
79 if encoded_value.len() > MAX_PRINCIPAL_FACT_BYTES_V1 {
80 return Err(PrincipalFactError::LimitExceeded);
81 }
82 Ok(Self {
83 name,
84 value,
85 encoded_value: encoded_value.into(),
86 })
87 }
88
89 #[doc(hidden)]
91 #[must_use]
92 pub fn name(&self) -> &str {
93 &self.name
94 }
95
96 #[doc(hidden)]
98 #[must_use]
99 pub const fn internal_value(&self) -> &CanonicalValue {
100 &self.value
101 }
102
103 #[doc(hidden)]
105 #[must_use]
106 pub fn internal_encoded_value(&self) -> &[u8] {
107 &self.encoded_value
108 }
109
110 fn is_narrowing_of(&self, parent: &Self) -> bool {
111 if self.name != parent.name {
112 return false;
113 }
114 match (&self.value, &parent.value) {
115 (CanonicalValue::List(child), CanonicalValue::List(parent)) => child
116 .values()
117 .iter()
118 .all(|candidate| parent.values().iter().any(|value| value == candidate)),
119 _ => self.encoded_value == parent.encoded_value,
120 }
121 }
122}
123
124impl fmt::Debug for CapabilityPrincipalFactV1 {
125 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126 formatter.write_str("CapabilityPrincipalFactV1([REDACTED])")
127 }
128}
129
130#[derive(Clone, Eq, PartialEq)]
132pub struct CapabilityPrincipalFactsV1 {
133 facts: crate::OwnedCapacitySliceV1<CapabilityPrincipalFactV1>,
134 canonical_bytes: crate::OwnedCapacitySliceV1<u8>,
135}
136impl crate::InspectOwnedCapacityV1 for CapabilityPrincipalFactsV1 {
137 fn inspect_owned_capacity(
138 &self,
139 inspection: &mut crate::OwnedCapacityInspectionV1,
140 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
141 let Self {
142 facts,
143 canonical_bytes,
144 } = self;
145 inspection.visit(facts)?;
146 inspection.visit(canonical_bytes)?;
147 Ok(())
148 }
149}
150
151impl CapabilityPrincipalFactsV1 {
152 pub fn new(mut facts: Vec<CapabilityPrincipalFactV1>) -> Result<Self, PrincipalFactError> {
154 if facts.len() > MAX_PRINCIPAL_FACTS_V1 {
155 return Err(PrincipalFactError::LimitExceeded);
156 }
157 facts.sort_unstable_by(|left, right| left.name.cmp(&right.name));
158 if facts.windows(2).any(|pair| pair[0].name == pair[1].name) {
159 return Err(PrincipalFactError::Duplicate);
160 }
161 let canonical_bytes = encode_set(&facts)?;
162 if canonical_bytes.len() > MAX_PRINCIPAL_FACT_BYTES_V1 {
163 return Err(PrincipalFactError::LimitExceeded);
164 }
165 Ok(Self {
166 facts: facts.into(),
167 canonical_bytes: canonical_bytes.into(),
168 })
169 }
170
171 #[must_use]
173 pub fn empty() -> Self {
174 Self::new(Vec::new()).expect("the empty principal fact set is bounded")
175 }
176
177 #[doc(hidden)]
179 pub fn names(&self) -> impl ExactSizeIterator<Item = &str> {
180 self.facts.iter().map(|fact| fact.name.as_str())
181 }
182
183 #[doc(hidden)]
185 pub fn decode_canonical(bytes: &[u8]) -> Result<Self, PrincipalFactError> {
186 if bytes.len() > MAX_PRINCIPAL_FACT_BYTES_V1 {
187 return Err(PrincipalFactError::LimitExceeded);
188 }
189 let mut reader = FactReader::new(bytes);
190 if reader.take(FACT_SET_MAGIC.len())? != FACT_SET_MAGIC
191 || reader.u32()? != PRINCIPAL_FACT_SET_VERSION_V1
192 {
193 return Err(PrincipalFactError::InvalidEncoding);
194 }
195 let count = reader.len(MAX_PRINCIPAL_FACTS_V1)?;
196 let mut facts = Vec::with_capacity(count);
197 for _ in 0..count {
198 let name = std::str::from_utf8(reader.bytes(MAX_PRINCIPAL_FACT_NAME_BYTES_V1)?)
199 .map_err(|_| PrincipalFactError::InvalidEncoding)?;
200 let value = decode_canonical_value(reader.bytes(MAX_PRINCIPAL_FACT_BYTES_V1)?)
201 .map_err(|_| PrincipalFactError::InvalidEncoding)?;
202 facts.push(CapabilityPrincipalFactV1::new(name, value)?);
203 }
204 if !reader.is_empty() {
205 return Err(PrincipalFactError::InvalidEncoding);
206 }
207 let decoded = Self::new(facts)?;
208 if decoded.canonical_bytes.as_ref() != bytes {
209 return Err(PrincipalFactError::InvalidEncoding);
210 }
211 Ok(decoded)
212 }
213
214 #[doc(hidden)]
216 #[must_use]
217 pub fn internal_fact(&self, name: &str) -> Option<&CapabilityPrincipalFactV1> {
218 self.facts
219 .binary_search_by(|fact| fact.name.as_str().cmp(name))
220 .ok()
221 .map(|index| &self.facts[index])
222 }
223
224 #[doc(hidden)]
226 #[must_use]
227 pub fn internal_canonical_bytes(&self) -> &[u8] {
228 &self.canonical_bytes
229 }
230
231 #[must_use]
234 pub fn is_narrowing_of(&self, parent: &Self) -> bool {
235 self.facts.iter().all(|child| {
236 parent
237 .facts
238 .binary_search_by(|fact| fact.name.cmp(&child.name))
239 .ok()
240 .is_some_and(|index| child.is_narrowing_of(&parent.facts[index]))
241 })
242 }
243}
244
245impl fmt::Debug for CapabilityPrincipalFactsV1 {
246 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
247 formatter
248 .debug_struct("CapabilityPrincipalFactsV1")
249 .field("facts", &"[REDACTED]")
250 .field("fact_count", &self.facts.len())
251 .finish()
252 }
253}
254
255fn canonicalize_value(value: CanonicalValue) -> Result<CanonicalValue, PrincipalFactError> {
256 match value {
257 CanonicalValue::List(list) => {
258 if list.len() > MAX_PRINCIPAL_FACT_VALUES_V1 {
259 return Err(PrincipalFactError::LimitExceeded);
260 }
261 let mut members = list
262 .values()
263 .iter()
264 .cloned()
265 .map(|member| {
266 if !is_scalar(&member) {
267 return Err(PrincipalFactError::InvalidValue);
268 }
269 let bytes = encode_canonical_value(&member)
270 .map_err(|_| PrincipalFactError::InvalidEncoding)?;
271 Ok((bytes, member))
272 })
273 .collect::<Result<Vec<_>, PrincipalFactError>>()?;
274 members.sort_unstable_by(|left, right| left.0.cmp(&right.0));
275 if members.windows(2).any(|pair| pair[0].0 == pair[1].0) {
276 return Err(PrincipalFactError::Duplicate);
277 }
278 CanonicalValue::list(members.into_iter().map(|(_, member)| member).collect())
279 .map_err(|_| PrincipalFactError::InvalidValue)
280 }
281 scalar if is_scalar(&scalar) => Ok(scalar),
282 _ => Err(PrincipalFactError::InvalidValue),
283 }
284}
285
286fn is_scalar(value: &CanonicalValue) -> bool {
287 matches!(
288 value,
289 CanonicalValue::Bool(_)
290 | CanonicalValue::I64(_)
291 | CanonicalValue::U64(_)
292 | CanonicalValue::Decimal(_)
293 | CanonicalValue::Money(_)
294 | CanonicalValue::String(_)
295 | CanonicalValue::Bytes(_)
296 | CanonicalValue::Timestamp(_)
297 | CanonicalValue::Date(_)
298 | CanonicalValue::Uuid(_)
299 | CanonicalValue::Enum { .. }
300 )
301}
302
303fn validate_name(name: &str) -> Result<(), PrincipalFactError> {
304 let mut bytes = name.bytes();
305 let first = bytes.next().ok_or(PrincipalFactError::InvalidName)?;
306 if name.len() > MAX_PRINCIPAL_FACT_NAME_BYTES_V1
307 || !(first == b'_' || first.is_ascii_alphabetic())
308 || bytes.any(|byte| !(byte == b'_' || byte.is_ascii_alphanumeric()))
309 {
310 return Err(PrincipalFactError::InvalidName);
311 }
312 Ok(())
313}
314
315fn encode_set(facts: &[CapabilityPrincipalFactV1]) -> Result<Vec<u8>, PrincipalFactError> {
316 let mut bytes = Vec::new();
317 bytes.extend_from_slice(FACT_SET_MAGIC);
318 bytes.extend_from_slice(&PRINCIPAL_FACT_SET_VERSION_V1.to_be_bytes());
319 push_len(&mut bytes, facts.len())?;
320 for fact in facts {
321 push_bytes(&mut bytes, fact.name.as_bytes())?;
322 push_bytes(&mut bytes, &fact.encoded_value)?;
323 if bytes.len() > MAX_PRINCIPAL_FACT_BYTES_V1 {
324 return Err(PrincipalFactError::LimitExceeded);
325 }
326 }
327 Ok(bytes)
328}
329
330fn push_bytes(bytes: &mut Vec<u8>, value: &[u8]) -> Result<(), PrincipalFactError> {
331 push_len(bytes, value.len())?;
332 bytes.extend_from_slice(value);
333 Ok(())
334}
335
336fn push_len(bytes: &mut Vec<u8>, value: usize) -> Result<(), PrincipalFactError> {
337 let value = u32::try_from(value).map_err(|_| PrincipalFactError::LimitExceeded)?;
338 bytes.extend_from_slice(&value.to_be_bytes());
339 Ok(())
340}
341
342struct FactReader<'a> {
343 bytes: &'a [u8],
344 offset: usize,
345}
346
347impl<'a> FactReader<'a> {
348 const fn new(bytes: &'a [u8]) -> Self {
349 Self { bytes, offset: 0 }
350 }
351
352 fn take(&mut self, len: usize) -> Result<&'a [u8], PrincipalFactError> {
353 let end = self
354 .offset
355 .checked_add(len)
356 .ok_or(PrincipalFactError::InvalidEncoding)?;
357 let value = self
358 .bytes
359 .get(self.offset..end)
360 .ok_or(PrincipalFactError::InvalidEncoding)?;
361 self.offset = end;
362 Ok(value)
363 }
364
365 fn u32(&mut self) -> Result<u32, PrincipalFactError> {
366 let bytes: [u8; 4] = self
367 .take(4)?
368 .try_into()
369 .map_err(|_| PrincipalFactError::InvalidEncoding)?;
370 Ok(u32::from_be_bytes(bytes))
371 }
372
373 fn len(&mut self, maximum: usize) -> Result<usize, PrincipalFactError> {
374 let len = usize::try_from(self.u32()?).map_err(|_| PrincipalFactError::LimitExceeded)?;
375 if len > maximum {
376 return Err(PrincipalFactError::LimitExceeded);
377 }
378 Ok(len)
379 }
380
381 fn bytes(&mut self, maximum: usize) -> Result<&'a [u8], PrincipalFactError> {
382 let len = self.len(maximum)?;
383 self.take(len)
384 }
385
386 fn is_empty(&self) -> bool {
387 self.offset == self.bytes.len()
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 fn text(value: &str) -> CanonicalValue {
396 CanonicalValue::string(value).expect("bounded text")
397 }
398
399 #[test]
400 fn fact_sets_are_canonical_redacted_and_narrowing_only() {
401 let parent = CapabilityPrincipalFactsV1::new(vec![
402 CapabilityPrincipalFactV1::new(
403 "groups",
404 CanonicalValue::list(vec![text("operators"), text("authors")])
405 .expect("bounded list"),
406 )
407 .expect("fact"),
408 CapabilityPrincipalFactV1::new("region", text("us-central")).expect("fact"),
409 ])
410 .expect("parent facts");
411 let child = CapabilityPrincipalFactsV1::new(vec![
412 CapabilityPrincipalFactV1::new(
413 "groups",
414 CanonicalValue::list(vec![text("authors")]).expect("bounded list"),
415 )
416 .expect("fact"),
417 ])
418 .expect("child facts");
419 let widened = CapabilityPrincipalFactsV1::new(vec![
420 CapabilityPrincipalFactV1::new(
421 "groups",
422 CanonicalValue::list(vec![text("authors"), text("owners")]).expect("bounded list"),
423 )
424 .expect("fact"),
425 ])
426 .expect("widened facts");
427
428 assert!(child.is_narrowing_of(&parent));
429 assert!(!widened.is_narrowing_of(&parent));
430 assert_eq!(parent.names().collect::<Vec<_>>(), ["groups", "region"]);
431 assert_eq!(
432 format!("{parent:?}"),
433 "CapabilityPrincipalFactsV1 { facts: \"[REDACTED]\", fact_count: 2 }"
434 );
435 assert!(!format!("{parent:?}").contains("authors"));
436 assert_eq!(
437 CapabilityPrincipalFactsV1::decode_canonical(parent.internal_canonical_bytes())
438 .expect("strict round trip"),
439 parent
440 );
441 }
442
443 #[test]
444 fn ordering_duplicates_and_limits_fail_closed() {
445 let first = CapabilityPrincipalFactsV1::new(vec![
446 CapabilityPrincipalFactV1::new("zeta", CanonicalValue::Bool(true)).expect("fact"),
447 CapabilityPrincipalFactV1::new("alpha", CanonicalValue::U64(7)).expect("fact"),
448 ])
449 .expect("facts");
450 let second = CapabilityPrincipalFactsV1::new(vec![
451 CapabilityPrincipalFactV1::new("alpha", CanonicalValue::U64(7)).expect("fact"),
452 CapabilityPrincipalFactV1::new("zeta", CanonicalValue::Bool(true)).expect("fact"),
453 ])
454 .expect("facts");
455 assert_eq!(
456 first.internal_canonical_bytes(),
457 second.internal_canonical_bytes()
458 );
459 assert_eq!(
460 CapabilityPrincipalFactsV1::new(vec![
461 CapabilityPrincipalFactV1::new("same", CanonicalValue::Bool(true)).expect("fact"),
462 CapabilityPrincipalFactV1::new("same", CanonicalValue::Bool(false)).expect("fact"),
463 ])
464 .expect_err("duplicate name"),
465 PrincipalFactError::Duplicate
466 );
467 assert_eq!(
468 CapabilityPrincipalFactV1::new("bad-name", CanonicalValue::Bool(true))
469 .expect_err("invalid name"),
470 PrincipalFactError::InvalidName
471 );
472 let mut trailing = first.internal_canonical_bytes().to_vec();
473 trailing.push(0);
474 assert_eq!(
475 CapabilityPrincipalFactsV1::decode_canonical(&trailing).expect_err("trailing bytes"),
476 PrincipalFactError::InvalidEncoding
477 );
478 for end in 0..first.internal_canonical_bytes().len() {
479 assert!(
480 CapabilityPrincipalFactsV1::decode_canonical(
481 &first.internal_canonical_bytes()[..end]
482 )
483 .is_err()
484 );
485 }
486 }
487}