templar_curator_primitives/rbac/
mod.rs1use alloc::vec::Vec;
15use templar_vault_kernel::Address;
16
17use crate::auth::{
18 canonical_policy_class, ActionKind, AuthAdapter, AuthError, AuthPolicyClass, AuthResult,
19};
20
21#[templar_vault_macros::vault_derive(borsh, schemars, serde)]
23#[derive(Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "boundary", derive(near_sdk::BorshStorageKey))]
25pub enum Role {
26 Curator,
28 Sentinel,
30 Allocator,
32}
33
34impl Role {
35 #[cfg(not(target_arch = "wasm32"))]
36 #[inline]
37 #[must_use]
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 Role::Curator => "curator",
41 Role::Sentinel => "sentinel",
42 Role::Allocator => "allocator",
43 }
44 }
45}
46
47#[templar_vault_macros::vault_derive(borsh, serde)]
49#[derive(Clone, PartialEq, Eq)]
50pub struct RoleAssignment {
51 pub address: Address,
53 pub role: Role,
55}
56
57#[templar_vault_macros::vault_derive]
59#[derive(Clone, Default)]
60pub struct RbacConfig {
61 pub assignments: Vec<RoleAssignment>,
63 pub paused: bool,
65}
66
67impl RbacConfig {
68 #[inline]
70 #[must_use]
71 pub fn with_curator(curator: Address) -> Self {
72 Self {
73 assignments: alloc::vec![RoleAssignment {
74 address: curator,
75 role: Role::Curator,
76 }],
77 paused: false,
78 }
79 }
80
81 #[inline]
83 pub fn add_role(&mut self, address: Address, role: Role) {
84 self.assignments
86 .retain(|a| !(a.address == address && a.role == role));
87 self.assignments.push(RoleAssignment { address, role });
88 }
89
90 #[inline]
92 pub fn remove_role(&mut self, address: &Address, role: Role) {
93 self.assignments
94 .retain(|assignment| !(assignment.address == *address && assignment.role == role));
95 }
96
97 #[inline]
99 #[must_use]
100 pub fn has_role(&self, address: &Address, role: Role) -> bool {
101 self.assignments
102 .iter()
103 .any(|assignment| assignment.address == *address && assignment.role == role)
104 }
105
106 #[inline]
107 #[must_use]
108 fn is_curator(&self, address: &Address) -> bool {
109 self.has_role(address, Role::Curator)
110 }
111
112 #[must_use]
114 pub fn get_roles(&self, address: &Address) -> Vec<Role> {
115 self.assignments
116 .iter()
117 .filter(|a| &a.address == address)
118 .map(|a| a.role)
119 .collect()
120 }
121
122 #[inline]
124 pub fn set_paused(&mut self, paused: bool) {
125 self.paused = paused;
126 }
127}
128
129#[inline]
134#[must_use]
135pub fn required_role(action: ActionKind) -> Option<Role> {
136 match canonical_policy_class(action) {
137 AuthPolicyClass::Public => None,
138 AuthPolicyClass::Sentinel => Some(Role::Sentinel),
139 AuthPolicyClass::Allocator | AuthPolicyClass::AllocatorEmergency => Some(Role::Allocator),
140 AuthPolicyClass::Curator => Some(Role::Curator),
141 }
142}
143
144#[templar_vault_macros::vault_derive]
149#[derive(Clone, Default)]
150pub struct RbacAuth {
151 pub config: RbacConfig,
153}
154
155impl RbacAuth {
156 #[inline]
157 fn is_allowed(&self, action: ActionKind, caller: &Address) -> bool {
158 match canonical_policy_class(action) {
159 AuthPolicyClass::Public => true,
160 AuthPolicyClass::Sentinel => self.config.has_role(caller, Role::Sentinel),
161 AuthPolicyClass::Allocator => {
162 self.config.has_role(caller, Role::Allocator) || self.config.is_curator(caller)
163 }
164 AuthPolicyClass::AllocatorEmergency => {
165 self.config.has_role(caller, Role::Allocator)
166 || self.config.has_role(caller, Role::Sentinel)
167 || self.config.is_curator(caller)
168 }
169 AuthPolicyClass::Curator => self.config.is_curator(caller),
170 }
171 }
172}
173
174impl AuthAdapter for RbacAuth {
175 fn authorize(
176 &self,
177 action: ActionKind,
178 caller: Address,
179 _proof: Option<&[u8]>,
180 ) -> AuthResult<()> {
181 if self.config.paused && action != ActionKind::Pause && !action.is_privileged() {
184 return Err(AuthError::VaultPaused);
185 }
186
187 if !self.is_allowed(action, &caller) {
188 return Err(AuthError::MissingRole);
189 }
190
191 Ok(())
192 }
193
194 fn is_paused(&self) -> bool {
195 self.config.paused
196 }
197}