mongreldb_core/
auth_state.rs1use crate::auth::Principal;
17use crate::error::{MongrelError, Result};
18use parking_lot::RwLock;
19use std::sync::Arc;
20
21#[derive(Clone, Debug)]
29pub struct AuthState {
30 inner: Arc<AuthStateInner>,
31}
32
33#[derive(Debug)]
34struct AuthStateInner {
35 require_auth: RwLock<bool>,
40 principal: RwLock<Option<Principal>>,
43}
44
45impl AuthState {
46 pub fn new(require_auth: bool, principal: Option<Principal>) -> Self {
49 Self {
50 inner: Arc::new(AuthStateInner {
51 require_auth: RwLock::new(require_auth),
52 principal: RwLock::new(principal),
53 }),
54 }
55 }
56
57 pub fn disabled() -> Self {
59 Self::new(false, None)
60 }
61
62 pub fn require_auth(&self) -> bool {
64 *self.inner.require_auth.read()
65 }
66
67 pub fn set_require_auth(&self, value: bool) {
70 *self.inner.require_auth.write() = value;
71 }
72
73 pub fn principal(&self) -> Option<Principal> {
75 self.inner.principal.read().clone()
76 }
77
78 pub fn set_principal(&self, principal: Option<Principal>) {
81 *self.inner.principal.write() = principal;
82 }
83
84 pub fn require(&self, table_name: &str, perm: RequiredPermission) -> Result<()> {
91 if !self.require_auth() {
92 return Ok(());
93 }
94 let guard = self.inner.principal.read();
95 let p = guard.as_ref().ok_or(MongrelError::AuthRequired)?;
96 let required = perm.into_permission(table_name);
97 let has = p.has_permission(&required);
98 if has {
99 Ok(())
100 } else {
101 Err(MongrelError::PermissionDenied {
102 required,
103 principal: p.username.clone(),
104 })
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum RequiredPermission {
118 Select,
120 Insert,
122 Update,
124 Delete,
126}
127
128impl RequiredPermission {
129 pub fn into_permission(self, table_name: &str) -> crate::auth::Permission {
132 use crate::auth::Permission;
133 let table = table_name.to_string();
134 match self {
135 RequiredPermission::Select => Permission::Select { table },
136 RequiredPermission::Insert => Permission::Insert { table },
137 RequiredPermission::Update => Permission::Update { table },
138 RequiredPermission::Delete => Permission::Delete { table },
139 }
140 }
141}
142
143pub trait TableAuthChecker: Send + Sync + std::fmt::Debug {
151 fn check(&self, table_name: &str, perm: RequiredPermission) -> Result<()>;
155}
156
157#[derive(Debug, Clone)]
161pub struct DefaultTableAuthChecker {
162 state: AuthState,
163}
164
165impl DefaultTableAuthChecker {
166 pub fn new(state: AuthState) -> Self {
167 Self { state }
168 }
169}
170
171impl TableAuthChecker for DefaultTableAuthChecker {
172 fn check(&self, table_name: &str, perm: RequiredPermission) -> Result<()> {
173 self.state.require(table_name, perm)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::auth::Permission;
181
182 #[test]
183 fn disabled_state_is_noop() {
184 let state = AuthState::disabled();
185 assert!(!state.require_auth());
186 state.require("orders", RequiredPermission::Select).unwrap();
187 state.require("orders", RequiredPermission::Insert).unwrap();
188 }
189
190 #[test]
191 fn enabled_state_with_no_principal_is_auth_required() {
192 let state = AuthState::new(true, None);
193 assert!(state.require_auth());
194 let err = state
195 .require("orders", RequiredPermission::Select)
196 .unwrap_err();
197 assert!(matches!(err, MongrelError::AuthRequired));
198 }
199
200 #[test]
201 fn enabled_state_denies_without_permission() {
202 let principal = Principal {
203 user_id: 0,
204 created_epoch: 0,
205 username: "alice".into(),
206 is_admin: false,
207 roles: vec![],
208 permissions: vec![Permission::Select {
209 table: "orders".into(),
210 }],
211 };
212 let state = AuthState::new(true, Some(principal));
213 state.require("orders", RequiredPermission::Select).unwrap();
215 let err = state
217 .require("orders", RequiredPermission::Insert)
218 .unwrap_err();
219 assert!(matches!(err, MongrelError::PermissionDenied { .. }));
220 }
221
222 #[test]
223 fn enabled_state_admin_bypasses() {
224 let principal = Principal {
225 user_id: 0,
226 created_epoch: 0,
227 username: "admin".into(),
228 is_admin: true,
229 roles: vec![],
230 permissions: vec![],
231 };
232 let state = AuthState::new(true, Some(principal));
233 state.require("orders", RequiredPermission::Select).unwrap();
234 state.require("orders", RequiredPermission::Insert).unwrap();
235 state.require("orders", RequiredPermission::Delete).unwrap();
236 }
237
238 #[test]
239 fn set_require_auth_propagates_to_clones() {
240 let state = AuthState::disabled();
241 let clone = state.clone();
242 assert!(!clone.require_auth());
243 state.set_require_auth(true);
244 assert!(clone.require_auth(), "clone sees the live flag");
245 }
246
247 #[test]
248 fn default_checker_delegates_to_state() {
249 let principal = Principal {
250 user_id: 0,
251 created_epoch: 0,
252 username: "alice".into(),
253 is_admin: false,
254 roles: vec![],
255 permissions: vec![Permission::Insert {
256 table: "orders".into(),
257 }],
258 };
259 let checker = DefaultTableAuthChecker::new(AuthState::new(true, Some(principal)));
260 checker.check("orders", RequiredPermission::Insert).unwrap();
261 let err = checker
262 .check("orders", RequiredPermission::Select)
263 .unwrap_err();
264 assert!(matches!(err, MongrelError::PermissionDenied { .. }));
265 }
266}