1use anyhow::Result;
5
6use serde::{Deserialize, Serialize};
7
8use super::super::RealmError;
9use crate::storage::db::get_database;
10
11const ANONYMOUS_ACTOR_TYPE: &str = "ANONCLNT";
12const VOICE_ACTOR_TYPE: &str = "VOICE";
13const CHAT_ACTOR_TYPE: &str = "CHAT";
14
15#[derive(Debug, Serialize, Deserialize, Default)]
19pub struct ActorAcl {
20 pub rowid: Option<i64>,
21 pub realm_id: u32,
23 pub source_realm_id: Option<u32>,
25 pub from_type: String,
26 pub to_type: String,
27 pub access: bool,
28}
29
30impl<'r> sqlx::FromRow<'r, sqlx::sqlite::SqliteRow> for ActorAcl {
31 fn from_row(row: &'r sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
32 use sqlx::Row;
33 let source_realm_id = row
34 .try_get::<Option<i64>, _>("source_realm_id")?
35 .and_then(|v| u32::try_from(v).ok());
36 Ok(Self {
37 rowid: row.try_get("rowid")?,
38 realm_id: row.try_get::<i64, _>("realm_id")?.try_into().unwrap(),
39 source_realm_id,
40 from_type: row.try_get("from_type")?,
41 to_type: row.try_get("to_type")?,
42 access: row.try_get::<i64, _>("access")? != 0,
43 })
44 }
45}
46
47impl ActorAcl {
48 pub fn new(realm_id: u32, from_type: String, to_type: String, access: bool) -> Self {
50 Self::new_with_source_realm(realm_id, Some(realm_id), from_type, to_type, access)
52 }
53
54 pub fn new_with_source_realm(
56 realm_id: u32,
57 source_realm_id: Option<u32>,
58 from_type: String,
59 to_type: String,
60 access: bool,
61 ) -> Self {
62 Self {
63 rowid: None,
64 realm_id,
65 source_realm_id,
66 from_type: from_type.to_string(),
67 to_type: to_type.to_string(),
68 access,
69 }
70 }
71
72 pub async fn save(&mut self) -> Result<i64, RealmError> {
80 let db = get_database();
81 let pool = db.get_pool();
82
83 if let Some(rowid) = self.rowid {
84 sqlx::query(
86 "UPDATE actoracl
87 SET realm_id = ?, source_realm_id = ?, from_type = ?, to_type = ?, access = ?
88 WHERE rowid = ?",
89 )
90 .bind(self.realm_id)
91 .bind(self.source_realm_id.map(i64::from))
92 .bind(&self.from_type)
93 .bind(&self.to_type)
94 .bind(if self.access { 1 } else { 0 })
95 .bind(rowid)
96 .execute(pool)
97 .await?;
98
99 Ok(rowid)
100 } else {
101 let result = sqlx::query(
103 "INSERT INTO actoracl (realm_id, source_realm_id, from_type, to_type, access)
104 VALUES (?, ?, ?, ?, ?)",
105 )
106 .bind(self.realm_id)
107 .bind(self.source_realm_id.map(i64::from))
108 .bind(&self.from_type)
109 .bind(&self.to_type)
110 .bind(if self.access { 1 } else { 0 })
111 .execute(pool)
112 .await?;
113
114 let new_rowid = result.last_insert_rowid();
115 self.rowid = Some(new_rowid);
116 Ok(new_rowid)
117 }
118 }
119
120 pub async fn delete_by_id(id: i64) -> Result<u64, RealmError> {
130 let db = get_database();
131 let pool = db.get_pool();
132
133 let result = sqlx::query("DELETE FROM actoracl WHERE rowid = ?")
134 .bind(id)
135 .execute(pool)
136 .await?;
137
138 let changes = result.rows_affected();
139 if changes > 0 {
140 Ok(changes)
141 } else {
142 Err(RealmError::NotFound)
143 }
144 }
145
146 pub async fn get(id: i64) -> Result<Option<Self>, RealmError> {
156 let db = get_database();
157 let pool = db.get_pool();
158
159 let result = sqlx::query_as::<_, ActorAcl>(
160 "SELECT rowid, realm_id, source_realm_id, from_type, to_type, access
161 FROM actoracl
162 WHERE rowid = ?",
163 )
164 .bind(id)
165 .fetch_optional(pool)
166 .await?;
167
168 Ok(result)
169 }
170
171 pub async fn get_by_realm(realm_id: u32) -> Result<Vec<Self>, RealmError> {
173 let db = get_database();
174 let pool = db.get_pool();
175
176 let acls = sqlx::query_as::<_, ActorAcl>(
177 "SELECT rowid, realm_id, source_realm_id, from_type, to_type, access
178 FROM actoracl
179 WHERE realm_id = ?",
180 )
181 .bind(realm_id)
182 .fetch_all(pool)
183 .await?;
184
185 Ok(acls)
186 }
187
188 pub async fn get_by_types(
190 target_realm_id: u32,
191 source_realm_id: u32,
192 from_type: &str,
193 to_type: &str,
194 ) -> Result<Option<Self>, RealmError> {
195 let db = get_database();
196 let pool = db.get_pool();
197
198 let result = sqlx::query_as::<_, ActorAcl>(
199 "SELECT rowid, realm_id, source_realm_id, from_type, to_type, access
200 FROM actoracl
201 WHERE realm_id = ? AND source_realm_id = ? AND from_type = ? AND to_type = ?
202 ORDER BY rowid DESC
203 LIMIT 1",
204 )
205 .bind(target_realm_id)
206 .bind(source_realm_id)
207 .bind(from_type)
208 .bind(to_type)
209 .fetch_optional(pool)
210 .await?;
211
212 Ok(result)
213 }
214
215 pub async fn delete_by_target(realm_id: u32, to_type: &str) -> Result<u64, RealmError> {
217 let db = get_database();
218 let pool = db.get_pool();
219
220 let result = sqlx::query("DELETE FROM actoracl WHERE realm_id = ? AND to_type = ?")
221 .bind(realm_id)
222 .bind(to_type)
223 .execute(pool)
224 .await?;
225
226 Ok(result.rows_affected())
227 }
228
229 pub fn access(&self) -> bool {
230 self.access
231 }
232
233 pub async fn can_discover(
249 source_realm_id: u32,
250 target_realm_id: u32,
251 from_type: &str,
252 to_type: &str,
253 ) -> Result<bool, RealmError> {
254 match Self::get_by_types(target_realm_id, source_realm_id, from_type, to_type).await? {
255 Some(acl) => {
256 crate::recording::debug!(
257 "ACL rule found: source_realm_id={}, target_realm_id={}, from_type={}, to_type={}, access={}",
258 source_realm_id,
259 target_realm_id,
260 from_type,
261 to_type,
262 acl.access()
263 );
264 Ok(acl.access())
265 }
266 None => {
267 crate::recording::debug!(
269 "No ACL rule found, denying discovery (default policy): source_realm_id={}, target_realm_id={}, from_type={}, to_type={}",
270 source_realm_id,
271 target_realm_id,
272 from_type,
273 to_type
274 );
275 Ok(false)
276 }
277 }
278 }
279}
280
281pub fn mock_actor_acl() -> Vec<ActorAcl> {
282 vec![
283 ActorAcl {
284 rowid: None,
285 realm_id: 2,
286 source_realm_id: Some(2),
287 from_type: ANONYMOUS_ACTOR_TYPE.to_string(),
288 to_type: VOICE_ACTOR_TYPE.to_string(),
289 access: true,
290 },
291 ActorAcl {
292 rowid: None,
293 realm_id: 2,
294 source_realm_id: Some(2),
295 from_type: ANONYMOUS_ACTOR_TYPE.to_string(),
296 to_type: CHAT_ACTOR_TYPE.to_string(),
297 access: true,
298 },
299 ActorAcl {
300 rowid: None,
301 realm_id: 2,
302 source_realm_id: Some(2),
303 from_type: ANONYMOUS_ACTOR_TYPE.to_string(),
304 to_type: ANONYMOUS_ACTOR_TYPE.to_string(),
305 access: false,
306 },
307 ActorAcl {
308 rowid: None,
309 realm_id: 2,
310 source_realm_id: Some(2),
311 from_type: CHAT_ACTOR_TYPE.to_string(),
312 to_type: ANONYMOUS_ACTOR_TYPE.to_string(),
313 access: true,
314 },
315 ActorAcl {
316 rowid: None,
317 realm_id: 2,
318 source_realm_id: Some(2),
319 from_type: CHAT_ACTOR_TYPE.to_string(),
320 to_type: VOICE_ACTOR_TYPE.to_string(),
321 access: true,
322 },
323 ActorAcl {
324 rowid: None,
325 realm_id: 2,
326 source_realm_id: Some(2),
327 from_type: CHAT_ACTOR_TYPE.to_string(),
328 to_type: CHAT_ACTOR_TYPE.to_string(),
329 access: true,
330 },
331 ActorAcl {
332 rowid: None,
333 realm_id: 2,
334 source_realm_id: Some(2),
335 from_type: VOICE_ACTOR_TYPE.to_string(),
336 to_type: ANONYMOUS_ACTOR_TYPE.to_string(),
337 access: true,
338 },
339 ActorAcl {
340 rowid: None,
341 realm_id: 2,
342 source_realm_id: Some(2),
343 from_type: VOICE_ACTOR_TYPE.to_string(),
344 to_type: CHAT_ACTOR_TYPE.to_string(),
345 access: true,
346 },
347 ActorAcl {
348 rowid: None,
349 realm_id: 2,
350 source_realm_id: Some(2),
351 from_type: VOICE_ACTOR_TYPE.to_string(),
352 to_type: VOICE_ACTOR_TYPE.to_string(),
353 access: true,
354 },
355 ]
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::realm::Realm;
362 use crate::util::test_utils::utils::setup_test_db;
363 use serial_test::serial;
364
365 #[tokio::test]
366 #[serial]
367 async fn test_actor_acl_crud() -> anyhow::Result<()> {
368 setup_test_db().await?;
369
370 let realm = Realm::create("acl_test_realm".to_string(), String::new()).await?;
372 let realm_id = realm.id;
373
374 let mut acl = ActorAcl::new(
376 realm_id,
377 "identified_client_user".to_string(),
378 "identified_client_room".to_string(),
379 true,
380 );
381 let acl_id = acl.save().await?;
382 assert!(acl.rowid.is_some());
383 assert_eq!(acl.rowid.unwrap(), acl_id);
384
385 let fetched_opt = ActorAcl::get(acl_id).await?;
387 assert!(fetched_opt.is_some());
388 let fetched = fetched_opt.unwrap();
389 assert_eq!(fetched.realm_id, realm_id);
390 assert_eq!(fetched.source_realm_id, Some(realm_id));
391 assert_eq!(fetched.from_type, "identified_client_user");
392 assert_eq!(fetched.to_type, "identified_client_room");
393 assert!(fetched.access);
394
395 acl.access = false;
397 let updated_acl_id = acl.save().await?;
398 assert_eq!(updated_acl_id, acl_id);
399
400 let reloaded_opt = ActorAcl::get(acl_id).await?;
401 assert!(reloaded_opt.is_some());
402 let reloaded = reloaded_opt.unwrap();
403 assert!(!reloaded.access);
404
405 let acls_for_realm = ActorAcl::get_by_realm(realm_id).await?;
407 assert_eq!(acls_for_realm.len(), 1);
408 assert_eq!(acls_for_realm[0].rowid, Some(acl_id));
409
410 let acl_by_types_opt = ActorAcl::get_by_types(
412 realm_id,
413 realm_id,
414 "identified_client_user",
415 "identified_client_room",
416 )
417 .await?;
418 assert!(acl_by_types_opt.is_some());
419 let acl_by_types = acl_by_types_opt.unwrap();
420 assert!(!acl_by_types.access);
421
422 ActorAcl::delete_by_id(acl_id).await?;
424 let deleted_acl_opt = ActorAcl::get(acl_id).await?;
425 assert!(deleted_acl_opt.is_none());
426
427 Ok(())
428 }
429
430 #[tokio::test]
431 #[serial]
432 async fn test_cross_realm_acl_allow_and_deny() -> anyhow::Result<()> {
433 setup_test_db().await?;
434
435 let source = Realm::create("acl_source_realm".to_string(), String::new()).await?;
436 let target = Realm::create("acl_target_realm".to_string(), String::new()).await?;
437 let source_realm = source.id;
438 let target_realm = target.id;
439
440 let mut acl = ActorAcl::new_with_source_realm(
441 target_realm,
442 Some(source_realm),
443 "acme:client".to_string(),
444 "acme:worker".to_string(),
445 true,
446 );
447 let _ = acl.save().await?;
448
449 let allowed =
450 ActorAcl::can_discover(source_realm, target_realm, "acme:client", "acme:worker")
451 .await?;
452 assert!(allowed);
453
454 let denied_same_realm =
455 ActorAcl::can_discover(target_realm, target_realm, "acme:client", "acme:worker")
456 .await?;
457 assert!(!denied_same_realm);
458
459 let denied_other_type =
460 ActorAcl::can_discover(source_realm, target_realm, "acme:other", "acme:worker").await?;
461 assert!(!denied_other_type);
462
463 Ok(())
464 }
465}