1use crate::types::{Ability, Capabilities, ResourceRef};
4use std::collections::HashMap;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum ResourcePattern {
8 All,
9 Kind(&'static str),
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Condition {
14 Always,
15 Owner,
16}
17
18#[derive(Clone, Debug)]
19struct Rule {
20 role: &'static str,
21 ability: Ability,
22 resource: ResourcePattern,
23 condition: Condition,
24 allow: bool,
25}
26
27#[derive(Clone, Debug, Default)]
28pub struct RoleMatrix {
29 rules: Vec<Rule>,
30 allow_all_roles: Vec<&'static str>,
31}
32
33impl RoleMatrix {
34 pub fn evaluate(
35 &self,
36 role: &str,
37 ability: Ability,
38 resource: Option<ResourceRef>,
39 is_owner: bool,
40 ) -> bool {
41 if self.allow_all_roles.contains(&role) {
42 return true;
43 }
44
45 let mut decided: Option<bool> = None;
46 for rule in &self.rules {
47 if rule.role != role || rule.ability != ability {
48 continue;
49 }
50 if !resource_matches(rule.resource, resource) {
51 continue;
52 }
53 let ok = match rule.condition {
54 Condition::Always => true,
55 Condition::Owner => is_owner,
56 };
57 if ok {
58 decided = Some(rule.allow);
59 }
60 }
61
62 decided.unwrap_or(false)
63 }
64
65 pub fn capabilities(
66 &self,
67 role: &str,
68 resource: Option<ResourceRef>,
69 is_owner: bool,
70 ) -> Capabilities {
71 Capabilities {
72 view: self.evaluate(role, Ability::View, resource, is_owner),
73 create: self.evaluate(role, Ability::Create, resource, is_owner),
74 update: self.evaluate(role, Ability::Update, resource, is_owner),
75 delete: self.evaluate(role, Ability::Delete, resource, is_owner),
76 manage: self.evaluate(role, Ability::Manage, resource, is_owner),
77 }
78 }
79}
80
81fn resource_matches(pattern: ResourcePattern, resource: Option<ResourceRef>) -> bool {
82 match pattern {
83 ResourcePattern::All => true,
84 ResourcePattern::Kind(kind) => resource.is_some_and(|r| r.kind == kind),
85 }
86}
87
88#[derive(Default)]
89pub struct MatrixBuilder {
90 kinds: HashMap<&'static str, RoleMatrix>,
91}
92
93impl MatrixBuilder {
94 pub fn allow_all(&mut self, scope_kind: &'static str, role: &'static str) -> &mut Self {
95 self.kinds
96 .entry(scope_kind)
97 .or_default()
98 .allow_all_roles
99 .push(role);
100 self
101 }
102
103 pub fn allow(
104 &mut self,
105 scope_kind: &'static str,
106 role: &'static str,
107 ability: Ability,
108 resource: ResourcePattern,
109 condition: Condition,
110 ) -> &mut Self {
111 self.kinds
112 .entry(scope_kind)
113 .or_default()
114 .rules
115 .push(Rule {
116 role,
117 ability,
118 resource,
119 condition,
120 allow: true,
121 });
122 self
123 }
124
125 pub fn deny(
126 &mut self,
127 scope_kind: &'static str,
128 role: &'static str,
129 ability: Ability,
130 resource: ResourcePattern,
131 condition: Condition,
132 ) -> &mut Self {
133 self.kinds
134 .entry(scope_kind)
135 .or_default()
136 .rules
137 .push(Rule {
138 role,
139 ability,
140 resource,
141 condition,
142 allow: false,
143 });
144 self
145 }
146
147 pub fn build(self) -> HashMap<&'static str, RoleMatrix> {
148 self.kinds
149 }
150}
151
152