1#[cfg(all(not(feature = "std"), feature = "alloc"))]
10use alloc::string::String;
11#[cfg(all(not(feature = "std"), feature = "alloc"))]
12use alloc::sync::Arc;
13#[cfg(all(not(feature = "std"), feature = "alloc"))]
14use alloc::vec::Vec;
15use core::fmt;
16
17#[cfg(feature = "std")]
18use std::sync::Arc;
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23use crate::audit::Principal;
24use crate::org_id::OrgId;
25use crate::request_id::RequestId;
26
27#[derive(Clone, PartialEq, Eq, Hash, Debug)]
37#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
38#[cfg_attr(feature = "utoipa", schema(value_type = String))]
39#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
40#[cfg_attr(feature = "schemars", schemars(transparent))]
41pub struct Role(Arc<str>);
42
43impl Role {
44 #[must_use]
55 pub fn as_str(&self) -> &str {
56 &self.0
57 }
58}
59
60impl From<&str> for Role {
61 fn from(s: &str) -> Self {
62 Self(Arc::from(s))
63 }
64}
65
66impl From<String> for Role {
67 fn from(s: String) -> Self {
68 Self(Arc::from(s.as_str()))
69 }
70}
71
72impl fmt::Display for Role {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(&self.0)
75 }
76}
77
78#[cfg(feature = "serde")]
79impl Serialize for Role {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: serde::Serializer,
83 {
84 self.0.serialize(serializer)
85 }
86}
87
88#[cfg(feature = "serde")]
89impl<'de> Deserialize<'de> for Role {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 let s = String::deserialize(deserializer)?;
95 Ok(Self(Arc::from(s.as_str())))
96 }
97}
98
99#[derive(Clone, PartialEq, Eq, Hash, Debug)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
107#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
108#[non_exhaustive]
109pub enum RoleScope {
110 Self_,
112 Subtree,
114 Specific(OrgId),
116}
117
118#[derive(Clone, PartialEq, Eq, Hash, Debug)]
124#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
125#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
126#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
127pub struct RoleBinding {
128 pub role: Role,
130 pub scope: RoleScope,
132}
133
134#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143#[non_exhaustive]
144pub enum AttestationKind {
145 Biscuit,
147 Jwt,
149 ApiKey,
151 Mtls,
153}
154
155#[derive(Clone, PartialEq, Eq, Debug)]
165#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
166pub struct Attestation {
167 pub kind: AttestationKind,
169 pub raw: Vec<u8>,
171}
172
173#[derive(Clone, PartialEq, Eq, Debug)]
207#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
208pub struct OrganizationContext {
209 pub org_id: OrgId,
211 pub principal: Principal,
213 pub request_id: RequestId,
215 pub roles: Vec<RoleBinding>,
217 #[cfg_attr(feature = "serde", serde(default))]
219 pub org_path: Vec<OrgId>,
220 pub attestation: Option<Attestation>,
222}
223
224impl OrganizationContext {
225 #[must_use]
248 pub fn new(org_id: OrgId, principal: Principal, request_id: RequestId) -> Self {
249 Self {
250 org_id,
251 principal,
252 request_id,
253 roles: Vec::new(),
254 org_path: Vec::new(),
255 attestation: None,
256 }
257 }
258
259 #[must_use]
278 pub fn with_roles(mut self, roles: Vec<RoleBinding>) -> Self {
279 self.roles = roles;
280 self
281 }
282
283 #[must_use]
303 pub fn with_org_path(mut self, org_path: Vec<OrgId>) -> Self {
304 self.org_path = org_path;
305 self
306 }
307
308 #[must_use]
330 pub fn with_attestation(mut self, attestation: Attestation) -> Self {
331 self.attestation = Some(attestation);
332 self
333 }
334}
335
336#[cfg(test)]
341mod tests {
342 use super::*;
343 use core::hash::{Hash, Hasher};
344 use std::collections::hash_map::DefaultHasher;
345
346 #[test]
348 fn role_scope_self_clone_eq() {
349 let s1 = RoleScope::Self_;
350 let s2 = s1.clone();
351 assert_eq!(s1, s2);
352 }
353
354 #[test]
355 fn role_scope_subtree_clone_eq() {
356 let s1 = RoleScope::Subtree;
357 let s2 = s1.clone();
358 assert_eq!(s1, s2);
359 }
360
361 #[test]
362 fn role_scope_specific_eq() {
363 let id = OrgId::generate();
364 let s1 = RoleScope::Specific(id);
365 let s2 = RoleScope::Specific(id);
366 assert_eq!(s1, s2);
367 }
368
369 #[cfg(feature = "serde")]
370 #[test]
371 fn role_scope_serde_roundtrip_self() {
372 let scope = RoleScope::Self_;
373 let json = serde_json::to_string(&scope).unwrap();
374 let back: RoleScope = serde_json::from_str(&json).unwrap();
375 assert_eq!(scope, back);
376 }
377
378 #[cfg(feature = "serde")]
379 #[test]
380 fn role_scope_serde_roundtrip_subtree() {
381 let scope = RoleScope::Subtree;
382 let json = serde_json::to_string(&scope).unwrap();
383 let back: RoleScope = serde_json::from_str(&json).unwrap();
384 assert_eq!(scope, back);
385 }
386
387 #[cfg(feature = "serde")]
388 #[test]
389 fn role_scope_serde_roundtrip_specific() {
390 let id = OrgId::generate();
391 let scope = RoleScope::Specific(id);
392 let json = serde_json::to_string(&scope).unwrap();
393 let back: RoleScope = serde_json::from_str(&json).unwrap();
394 assert_eq!(scope, back);
395 }
396
397 #[test]
399 fn role_binding_construction() {
400 let binding = RoleBinding {
401 role: Role::from("admin"),
402 scope: RoleScope::Self_,
403 };
404 assert_eq!(binding.role, Role::from("admin"));
405 assert_eq!(binding.scope, RoleScope::Self_);
406 }
407
408 #[cfg(feature = "serde")]
409 #[test]
410 fn role_binding_serde_roundtrip() {
411 let binding = RoleBinding {
412 role: Role::from("editor"),
413 scope: RoleScope::Subtree,
414 };
415 let json = serde_json::to_string(&binding).unwrap();
416 let back: RoleBinding = serde_json::from_str(&json).unwrap();
417 assert_eq!(binding, back);
418 }
419
420 #[test]
422 fn role_construction_from_str() {
423 let role = Role::from("admin");
424 assert_eq!(role.as_str(), "admin");
425 }
426
427 #[test]
428 fn role_construction_from_string() {
429 let role = Role::from("viewer".to_owned());
430 assert_eq!(role.as_str(), "viewer");
431 }
432
433 #[test]
434 fn role_clone_eq() {
435 let role1 = Role::from("editor");
436 let role2 = role1.clone();
437 assert_eq!(role1, role2);
438 }
439
440 #[test]
441 fn role_hash_eq() {
442 let role1 = Role::from("admin");
443 let role2 = Role::from("admin");
444
445 let mut hasher1 = DefaultHasher::new();
446 role1.hash(&mut hasher1);
447 let hash1 = hasher1.finish();
448
449 let mut hasher2 = DefaultHasher::new();
450 role2.hash(&mut hasher2);
451 let hash2 = hasher2.finish();
452
453 assert_eq!(hash1, hash2);
454 }
455
456 #[test]
457 fn role_display() {
458 let role = Role::from("admin");
459 assert_eq!(format!("{role}"), "admin");
460 }
461
462 #[test]
463 fn role_debug() {
464 let role = Role::from("admin");
465 let debug_str = format!("{role:?}");
466 assert!(debug_str.contains("admin"));
467 }
468
469 #[test]
471 fn attestation_kind_copy() {
472 let kind1 = AttestationKind::Jwt;
473 let kind2 = kind1;
474 assert_eq!(kind1, kind2);
475 }
476
477 #[test]
478 fn attestation_kind_all_variants() {
479 match AttestationKind::Biscuit {
480 AttestationKind::Biscuit => {}
481 _ => panic!("expected Biscuit"),
482 }
483 match AttestationKind::Jwt {
484 AttestationKind::Jwt => {}
485 _ => panic!("expected Jwt"),
486 }
487 match AttestationKind::ApiKey {
488 AttestationKind::ApiKey => {}
489 _ => panic!("expected ApiKey"),
490 }
491 match AttestationKind::Mtls {
492 AttestationKind::Mtls => {}
493 _ => panic!("expected Mtls"),
494 }
495 }
496
497 #[test]
499 fn attestation_construction() {
500 let att = Attestation {
501 kind: AttestationKind::Jwt,
502 raw: vec![1, 2, 3],
503 };
504 assert_eq!(att.kind, AttestationKind::Jwt);
505 assert_eq!(att.raw, vec![1, 2, 3]);
506 }
507
508 #[test]
509 fn attestation_clone_eq() {
510 let att1 = Attestation {
511 kind: AttestationKind::ApiKey,
512 raw: vec![42],
513 };
514 let att2 = att1.clone();
515 assert_eq!(att1, att2);
516 }
517
518 #[test]
519 fn attestation_debug() {
520 let att = Attestation {
521 kind: AttestationKind::Jwt,
522 raw: vec![],
523 };
524 let debug_str = format!("{att:?}");
525 assert!(debug_str.contains("Jwt"));
526 }
527
528 #[test]
530 fn org_context_construction() {
531 let org_id = OrgId::new(uuid::Uuid::nil());
532 let principal = Principal::system("test");
533 let request_id = RequestId::new();
534
535 let ctx = OrganizationContext::new(org_id, principal.clone(), request_id);
536
537 assert_eq!(ctx.org_id, org_id);
538 assert_eq!(ctx.principal, principal);
539 assert_eq!(ctx.request_id, request_id);
540 assert!(ctx.roles.is_empty());
541 assert!(ctx.attestation.is_none());
542 }
543
544 #[test]
545 fn org_context_with_role_bindings() {
546 let org_id = OrgId::generate();
547 let principal = Principal::system("test");
548 let request_id = RequestId::new();
549 let roles = vec![
550 RoleBinding {
551 role: Role::from("admin"),
552 scope: RoleScope::Self_,
553 },
554 RoleBinding {
555 role: Role::from("editor"),
556 scope: RoleScope::Subtree,
557 },
558 ];
559
560 let ctx = OrganizationContext::new(org_id, principal, request_id).with_roles(roles);
561
562 assert_eq!(ctx.roles.len(), 2);
563 assert_eq!(ctx.roles[0].role, Role::from("admin"));
564 assert_eq!(ctx.roles[1].role, Role::from("editor"));
565 }
566
567 #[test]
568 fn org_context_default_empty_org_path() {
569 let org_id = OrgId::generate();
570 let principal = Principal::system("test");
571 let request_id = RequestId::new();
572
573 let ctx = OrganizationContext::new(org_id, principal, request_id);
574
575 assert!(ctx.org_path.is_empty());
576 }
577
578 #[test]
579 fn org_context_with_org_path() {
580 let org_id = OrgId::generate();
581 let principal = Principal::system("test");
582 let request_id = RequestId::new();
583
584 let ctx =
585 OrganizationContext::new(org_id, principal, request_id).with_org_path(vec![org_id]);
586
587 assert_eq!(ctx.org_path.len(), 1);
588 assert_eq!(ctx.org_path[0], org_id);
589 }
590
591 #[test]
592 fn org_context_with_attestation() {
593 let org_id = OrgId::generate();
594 let principal = Principal::system("test");
595 let request_id = RequestId::new();
596 let att = Attestation {
597 kind: AttestationKind::ApiKey,
598 raw: vec![42],
599 };
600
601 let ctx =
602 OrganizationContext::new(org_id, principal, request_id).with_attestation(att.clone());
603
604 assert!(ctx.attestation.is_some());
605 assert_eq!(ctx.attestation.unwrap(), att);
606 }
607
608 #[test]
609 fn org_context_clone_eq() {
610 let org_id = OrgId::generate();
611 let principal = Principal::system("test");
612 let request_id = RequestId::new();
613
614 let ctx1 =
615 OrganizationContext::new(org_id, principal, request_id).with_roles(vec![RoleBinding {
616 role: Role::from("viewer"),
617 scope: RoleScope::Self_,
618 }]);
619 let ctx2 = ctx1.clone();
620
621 assert_eq!(ctx1, ctx2);
622 }
623
624 #[test]
625 fn org_context_debug() {
626 let org_id = OrgId::generate();
627 let principal = Principal::system("test");
628 let request_id = RequestId::new();
629
630 let ctx = OrganizationContext::new(org_id, principal, request_id);
631 let debug_str = format!("{ctx:?}");
632 assert!(debug_str.contains("OrganizationContext"));
633 }
634
635 #[test]
636 fn org_context_no_attestation() {
637 let org_id = OrgId::generate();
638 let principal = Principal::system("test");
639 let request_id = RequestId::new();
640
641 let ctx = OrganizationContext::new(org_id, principal, request_id);
642
643 assert!(ctx.attestation.is_none());
644 }
645
646 #[cfg(feature = "serde")]
648 #[test]
649 fn role_serde_roundtrip() {
650 let role = Role::from("admin");
651 let json = serde_json::to_string(&role).unwrap();
652 let back: Role = serde_json::from_str(&json).unwrap();
653 assert_eq!(role, back);
654 }
655
656 #[cfg(feature = "serde")]
657 #[test]
658 fn attestation_kind_serde_roundtrip_jwt() {
659 let kind = AttestationKind::Jwt;
660 let json = serde_json::to_string(&kind).unwrap();
661 let back: AttestationKind = serde_json::from_str(&json).unwrap();
662 assert_eq!(kind, back);
663 }
664
665 #[cfg(feature = "serde")]
666 #[test]
667 fn attestation_serde_roundtrip() {
668 let att = Attestation {
669 kind: AttestationKind::ApiKey,
670 raw: vec![1, 2, 3],
671 };
672 let json = serde_json::to_string(&att).unwrap();
673 let back: Attestation = serde_json::from_str(&json).unwrap();
674 assert_eq!(att, back);
675 }
676
677 #[cfg(feature = "serde")]
678 #[test]
679 fn org_context_serde_roundtrip() {
680 let org_id = OrgId::new(uuid::Uuid::nil());
681 let principal = Principal::system("test");
682 let request_id = RequestId::new();
683
684 let ctx = OrganizationContext::new(org_id, principal, request_id)
685 .with_roles(vec![RoleBinding {
686 role: Role::from("admin"),
687 scope: RoleScope::Self_,
688 }])
689 .with_org_path(vec![org_id])
690 .with_attestation(Attestation {
691 kind: AttestationKind::Jwt,
692 raw: vec![42],
693 });
694
695 let json = serde_json::to_string(&ctx).unwrap();
696 let back: OrganizationContext = serde_json::from_str(&json).unwrap();
697 assert_eq!(ctx, back);
698 }
699}