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 owner_pid: u32,
36 require_auth: RwLock<bool>,
41 principal: RwLock<Option<Principal>>,
44}
45
46impl AuthState {
47 pub fn new(require_auth: bool, principal: Option<Principal>) -> Self {
50 Self {
51 inner: Arc::new(AuthStateInner {
52 owner_pid: std::process::id(),
53 require_auth: RwLock::new(require_auth),
54 principal: RwLock::new(principal),
55 }),
56 }
57 }
58
59 pub fn disabled() -> Self {
61 Self::new(false, None)
62 }
63
64 pub fn require_auth(&self) -> bool {
66 *self.inner.require_auth.read()
67 }
68
69 pub fn set_require_auth(&self, value: bool) {
72 *self.inner.require_auth.write() = value;
73 }
74
75 pub fn principal(&self) -> Option<Principal> {
77 self.inner.principal.read().clone()
78 }
79
80 pub fn set_principal(&self, principal: Option<Principal>) {
83 *self.inner.principal.write() = principal;
84 }
85
86 pub fn require(&self, table_name: &str, perm: RequiredPermission) -> Result<()> {
93 let current_pid = std::process::id();
94 if current_pid != self.inner.owner_pid {
95 return Err(MongrelError::ForkedProcess {
96 owner_pid: self.inner.owner_pid,
97 current_pid,
98 });
99 }
100 if !self.require_auth() {
101 return Ok(());
102 }
103 let guard = self.inner.principal.read();
104 let p = guard.as_ref().ok_or(MongrelError::AuthRequired)?;
105 let required = perm.into_permission(table_name);
106 let has = p.has_permission(&required);
107 if has {
108 Ok(())
109 } else {
110 Err(MongrelError::PermissionDenied {
111 required,
112 principal: p.username.clone(),
113 })
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum RequiredPermission {
127 Select,
129 Insert,
131 Update,
133 Delete,
135}
136
137impl RequiredPermission {
138 pub fn into_permission(self, table_name: &str) -> crate::auth::Permission {
141 use crate::auth::Permission;
142 let table = table_name.to_string();
143 match self {
144 RequiredPermission::Select => Permission::Select { table },
145 RequiredPermission::Insert => Permission::Insert { table },
146 RequiredPermission::Update => Permission::Update { table },
147 RequiredPermission::Delete => Permission::Delete { table },
148 }
149 }
150}
151
152pub trait TableAuthChecker: Send + Sync + std::fmt::Debug {
160 fn check(&self, table_name: &str, perm: RequiredPermission) -> Result<()>;
164}
165
166#[derive(Debug, Clone)]
170pub struct DefaultTableAuthChecker {
171 state: AuthState,
172}
173
174impl DefaultTableAuthChecker {
175 pub fn new(state: AuthState) -> Self {
176 Self { state }
177 }
178}
179
180impl TableAuthChecker for DefaultTableAuthChecker {
181 fn check(&self, table_name: &str, perm: RequiredPermission) -> Result<()> {
182 self.state.require(table_name, perm)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::auth::Permission;
190
191 #[test]
192 fn disabled_state_is_noop() {
193 let state = AuthState::disabled();
194 assert!(!state.require_auth());
195 state.require("orders", RequiredPermission::Select).unwrap();
196 state.require("orders", RequiredPermission::Insert).unwrap();
197 }
198
199 #[test]
200 fn enabled_state_with_no_principal_is_auth_required() {
201 let state = AuthState::new(true, None);
202 assert!(state.require_auth());
203 let err = state
204 .require("orders", RequiredPermission::Select)
205 .unwrap_err();
206 assert!(matches!(err, MongrelError::AuthRequired));
207 }
208
209 #[test]
210 fn enabled_state_denies_without_permission() {
211 let principal = Principal {
212 user_id: 0,
213 created_epoch: 0,
214 username: "alice".into(),
215 is_admin: false,
216 roles: vec![],
217 permissions: vec![Permission::Select {
218 table: "orders".into(),
219 }],
220 };
221 let state = AuthState::new(true, Some(principal));
222 state.require("orders", RequiredPermission::Select).unwrap();
224 let err = state
226 .require("orders", RequiredPermission::Insert)
227 .unwrap_err();
228 assert!(matches!(err, MongrelError::PermissionDenied { .. }));
229 }
230
231 #[test]
232 fn enabled_state_admin_bypasses() {
233 let principal = Principal {
234 user_id: 0,
235 created_epoch: 0,
236 username: "admin".into(),
237 is_admin: true,
238 roles: vec![],
239 permissions: vec![],
240 };
241 let state = AuthState::new(true, Some(principal));
242 state.require("orders", RequiredPermission::Select).unwrap();
243 state.require("orders", RequiredPermission::Insert).unwrap();
244 state.require("orders", RequiredPermission::Delete).unwrap();
245 }
246
247 #[test]
248 fn set_require_auth_propagates_to_clones() {
249 let state = AuthState::disabled();
250 let clone = state.clone();
251 assert!(!clone.require_auth());
252 state.set_require_auth(true);
253 assert!(clone.require_auth(), "clone sees the live flag");
254 }
255
256 #[test]
257 fn default_checker_delegates_to_state() {
258 let principal = Principal {
259 user_id: 0,
260 created_epoch: 0,
261 username: "alice".into(),
262 is_admin: false,
263 roles: vec![],
264 permissions: vec![Permission::Insert {
265 table: "orders".into(),
266 }],
267 };
268 let checker = DefaultTableAuthChecker::new(AuthState::new(true, Some(principal)));
269 checker.check("orders", RequiredPermission::Insert).unwrap();
270 let err = checker
271 .check("orders", RequiredPermission::Select)
272 .unwrap_err();
273 assert!(matches!(err, MongrelError::PermissionDenied { .. }));
274 }
275}