Skip to main content

coil_auth/explain/
model.rs

1use super::*;
2
3pub const DEFAULT_EXPLAIN_MAX_DEPTH: usize = 100;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct ExplainOptions {
7    pub max_depth: usize,
8    pub cycle_protection: bool,
9}
10
11impl ExplainOptions {
12    pub const fn new(max_depth: usize) -> Self {
13        Self {
14            max_depth,
15            cycle_protection: true,
16        }
17    }
18
19    pub const fn with_cycle_protection(mut self, cycle_protection: bool) -> Self {
20        self.cycle_protection = cycle_protection;
21        self
22    }
23
24    pub const fn normalized(self) -> Self {
25        let max_depth = if self.max_depth == 0 {
26            1
27        } else {
28            self.max_depth
29        };
30
31        Self {
32            max_depth,
33            cycle_protection: self.cycle_protection,
34        }
35    }
36}
37
38impl Default for ExplainOptions {
39    fn default() -> Self {
40        Self {
41            max_depth: DEFAULT_EXPLAIN_MAX_DEPTH,
42            cycle_protection: true,
43        }
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ExplainDecision {
49    Allow,
50    Deny,
51}
52
53impl ExplainDecision {
54    pub const fn is_allowed(self) -> bool {
55        matches!(self, Self::Allow)
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
60pub struct ExplainedNode {
61    pub object: Entity,
62    pub relation: Option<Relation>,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ExplainStep {
67    Start {
68        node: ExplainedNode,
69    },
70    DirectSubjectMatch {
71        node: ExplainedNode,
72    },
73    TupleSubjectMatch {
74        from: ExplainedNode,
75        tuple: DefaultTuple,
76    },
77    Inherit {
78        from: ExplainedNode,
79        to: ExplainedNode,
80    },
81    TupleTraversal {
82        from: ExplainedNode,
83        tuple: DefaultTuple,
84        to: ExplainedNode,
85    },
86    Computed {
87        from: ExplainedNode,
88        via_tuple: DefaultTuple,
89        to: ExplainedNode,
90    },
91    TupleToUserset {
92        from: ExplainedNode,
93        via_tuple: DefaultTuple,
94        to: ExplainedNode,
95    },
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum DeniedReason {
100    NoMatchingPath,
101    RecursionLimitReached { max_depth: usize },
102    CycleDetected,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum DeniedAttempt {
107    Inherit {
108        step: ExplainStep,
109        result: Box<DeniedExplanation>,
110    },
111    TupleTraversal {
112        step: ExplainStep,
113        result: Box<DeniedExplanation>,
114    },
115    Computed {
116        step: ExplainStep,
117        result: Box<DeniedExplanation>,
118    },
119    TupleToUserset {
120        step: ExplainStep,
121        result: Box<DeniedExplanation>,
122    },
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct DeniedExplanation {
127    pub node: ExplainedNode,
128    pub reason: DeniedReason,
129    pub attempts: Vec<DeniedAttempt>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct AllowedExplanation {
134    pub steps: Vec<ExplainStep>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum ExplainTrace {
139    Allowed(AllowedExplanation),
140    Denied(DeniedExplanation),
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct CapabilityExplanation {
145    pub manifest: AuthModelManifest,
146    pub subject: DefaultSubject,
147    pub capability: Capability,
148    pub object: Entity,
149    pub binding: CapabilityBinding,
150    pub decision: ExplainDecision,
151    pub options: ExplainOptions,
152    pub trace: ExplainTrace,
153}