1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11use super::errors::AuthError;
12use crate::{
13 Result,
14 auth::{
15 crypto::PublicKey,
16 types::{AuthKey, DelegatedTreeRef, KeyHint, KeyStatus, Permission, ResolvedAuth, SigKey},
17 },
18 crdt::{Doc, doc::Value},
19 entry::ID,
20};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AuthSettings {
29 inner: Doc,
31}
32
33impl From<Doc> for AuthSettings {
34 fn from(doc: Doc) -> Self {
35 Self { inner: doc }
36 }
37}
38
39impl From<AuthSettings> for Doc {
40 fn from(settings: AuthSettings) -> Doc {
41 settings.inner
42 }
43}
44
45impl AuthSettings {
46 pub fn new() -> Self {
48 Self { inner: Doc::new() }
49 }
50
51 pub fn as_doc(&self) -> &Doc {
53 &self.inner
54 }
55
56 pub fn as_doc_mut(&mut self) -> &mut Doc {
58 &mut self.inner
59 }
60
61 pub fn add_key(&mut self, pubkey: &PublicKey, key: AuthKey) -> Result<()> {
69 let pubkey_str = pubkey.to_string();
70
71 if self.get_key_by_str(&pubkey_str).is_ok() {
73 return Err(AuthError::KeyAlreadyExists {
74 key_name: pubkey_str,
75 }
76 .into());
77 }
78
79 self.inner.set(format!("keys.{pubkey_str}"), key);
80 Ok(())
81 }
82
83 pub fn overwrite_key(&mut self, pubkey: &PublicKey, key: AuthKey) -> Result<()> {
85 let pubkey_str = pubkey.to_string();
86 self.inner.set(format!("keys.{pubkey_str}"), key);
87 Ok(())
88 }
89
90 pub fn get_key_by_pubkey(&self, pubkey: &PublicKey) -> Result<AuthKey> {
92 self.get_key_by_str(&pubkey.to_string())
93 }
94
95 fn get_key_by_str(&self, pubkey: &str) -> Result<AuthKey> {
100 match self.inner.get(format!("keys.{pubkey}")) {
101 Some(Value::Doc(doc)) => AuthKey::try_from(doc).map_err(|e| {
102 AuthError::InvalidKeyFormat {
103 reason: e.to_string(),
104 }
105 .into()
106 }),
107 Some(_) => Err(AuthError::InvalidKeyFormat {
108 reason: format!("key '{pubkey}' is not a Doc"),
109 }
110 .into()),
111 None => Err(AuthError::KeyNotFound {
112 key_name: pubkey.to_string(),
113 }
114 .into()),
115 }
116 }
117
118 pub fn find_keys_by_name(&self, name: &str) -> Vec<(String, AuthKey)> {
122 let mut matches = Vec::new();
123
124 if let Ok(all_keys) = self.get_all_keys() {
126 for (pubkey, auth_key) in all_keys {
127 if auth_key.name() == Some(name) {
128 matches.push((pubkey, auth_key));
129 }
130 }
131 }
132
133 matches.sort_by(|a, b| a.0.cmp(&b.0));
135 matches
136 }
137
138 pub fn get_all_keys(&self) -> Result<HashMap<String, AuthKey>> {
140 let mut result: HashMap<String, AuthKey> = HashMap::new();
141
142 if let Some(Value::Doc(keys_doc)) = self.inner.get("keys") {
144 for (pubkey, value) in keys_doc.iter() {
145 if let Value::Doc(key_doc) = value
146 && let Ok(auth_key) = AuthKey::try_from(key_doc)
147 {
148 result.insert(pubkey.clone(), auth_key);
149 }
150 }
151 }
152
153 Ok(result)
154 }
155
156 pub fn rename_key(&mut self, pubkey: &PublicKey, name: Option<&str>) -> Result<()> {
161 let pubkey_str = pubkey.to_string();
162 let mut auth_key = self.get_key_by_str(&pubkey_str)?;
163 auth_key.set_name(name);
164 self.inner.set(format!("keys.{pubkey_str}"), auth_key);
165 Ok(())
166 }
167
168 pub fn revoke_key(&mut self, pubkey: &PublicKey) -> Result<()> {
170 let pubkey_str = pubkey.to_string();
171 let mut auth_key = self.get_key_by_str(&pubkey_str)?;
172 auth_key.set_status(KeyStatus::Revoked);
173 self.inner.set(format!("keys.{pubkey_str}"), auth_key);
174 Ok(())
175 }
176
177 pub fn add_delegated_tree(&mut self, tree_ref: DelegatedTreeRef) -> Result<()> {
184 let root_id = tree_ref.tree.root.to_string();
185 self.inner.set(format!("delegations.{root_id}"), tree_ref);
186 Ok(())
187 }
188
189 pub fn get_delegated_tree(&self, root_id: &ID) -> Result<DelegatedTreeRef> {
191 match self.inner.get(format!("delegations.{root_id}")) {
192 Some(Value::Doc(doc)) => DelegatedTreeRef::try_from(doc).map_err(|e| {
193 AuthError::InvalidAuthConfiguration {
194 reason: format!("Invalid delegated tree format: {e}"),
195 }
196 .into()
197 }),
198 Some(_) => Err(AuthError::InvalidAuthConfiguration {
199 reason: format!("delegation '{root_id}' is not a Doc"),
200 }
201 .into()),
202 None => Err(AuthError::DelegationNotFound {
203 tree_id: root_id.clone(),
204 }
205 .into()),
206 }
207 }
208
209 pub fn get_all_delegated_trees(&self) -> Result<HashMap<ID, DelegatedTreeRef>> {
213 let mut result: HashMap<ID, DelegatedTreeRef> = HashMap::new();
214
215 if let Some(Value::Doc(delegations_doc)) = self.inner.get("delegations") {
217 for (root_id_str, value) in delegations_doc.iter() {
218 if let Value::Doc(doc) = value
219 && let Ok(tree_ref) = DelegatedTreeRef::try_from(doc)
220 {
221 let root_id = ID::parse(root_id_str)?;
222 result.insert(root_id, tree_ref);
223 }
224 }
225 }
226
227 Ok(result)
228 }
229
230 pub fn set_global_permission(&mut self, key: AuthKey) {
237 self.inner.set("global", key);
238 }
239
240 pub fn get_global_key(&self) -> Result<AuthKey> {
244 match self.inner.get("global") {
245 Some(Value::Doc(doc)) => AuthKey::try_from(doc).map_err(|e| {
246 AuthError::InvalidKeyFormat {
247 reason: e.to_string(),
248 }
249 .into()
250 }),
251 Some(_) => Err(AuthError::InvalidKeyFormat {
252 reason: "global key is not a Doc".to_string(),
253 }
254 .into()),
255 None => Err(AuthError::KeyNotFound {
256 key_name: "global".to_string(),
257 }
258 .into()),
259 }
260 }
261
262 pub fn resolve_hint(&self, hint: &KeyHint) -> Result<Vec<ResolvedAuth>> {
276 if hint.is_global() {
278 let global_key =
280 self.get_global_key()
281 .map_err(|_| AuthError::InvalidAuthConfiguration {
282 reason: "Global hint used but no global permission configured".to_string(),
283 })?;
284
285 let actual_pubkey =
287 hint.pubkey
288 .as_ref()
289 .ok_or_else(|| AuthError::InvalidAuthConfiguration {
290 reason: "Global hint has no pubkey".to_string(),
291 })?;
292
293 return Ok(vec![ResolvedAuth {
296 public_key: actual_pubkey.clone(),
297 effective_permission: *global_key.permissions(),
298 key_status: global_key.status().clone(),
299 }]);
300 }
301
302 if let Some(pubkey) = &hint.pubkey {
304 return match self.get_key_by_pubkey(pubkey) {
305 Ok(key) => Ok(vec![ResolvedAuth {
306 public_key: pubkey.clone(),
307 effective_permission: *key.permissions(),
308 key_status: key.status().clone(),
309 }]),
310 Err(e) => Err(e),
311 };
312 }
313
314 if let Some(name) = &hint.name {
316 let matches = self.find_keys_by_name(name);
317 if matches.is_empty() {
318 return Err(AuthError::KeyNotFound {
319 key_name: name.clone(),
320 }
321 .into());
322 }
323 let mut results = Vec::with_capacity(matches.len());
325 for (pubkey, auth_key) in matches {
326 results.push(ResolvedAuth {
327 public_key: PublicKey::from_prefixed_string(&pubkey)?,
328 effective_permission: *auth_key.permissions(),
329 key_status: auth_key.status().clone(),
330 });
331 }
332 return Ok(results);
333 }
334
335 Ok(vec![])
337 }
338
339 pub fn has_global_permission(&self) -> bool {
343 self.get_global_permission().is_some()
344 }
345
346 pub fn get_global_permission(&self) -> Option<Permission> {
348 if let Ok(key) = self.get_global_key()
349 && *key.status() == KeyStatus::Active
350 {
351 Some(*key.permissions())
352 } else {
353 None
354 }
355 }
356
357 pub fn global_permission_grants_access(&self, requested_permission: &Permission) -> bool {
359 if let Some(global_perm) = self.get_global_permission() {
360 global_perm >= *requested_permission
361 } else {
362 false
363 }
364 }
365
366 pub fn can_access(&self, pubkey: &PublicKey, requested_permission: &Permission) -> bool {
370 if let Ok(auth_key) = self.get_key_by_pubkey(pubkey)
372 && *auth_key.status() == KeyStatus::Active
373 && *auth_key.permissions() >= *requested_permission
374 {
375 return true;
376 }
377
378 self.global_permission_grants_access(requested_permission)
380 }
381
382 pub fn find_all_sigkeys_for_pubkey(&self, pubkey: &PublicKey) -> Vec<(SigKey, Permission)> {
386 let mut results = Vec::new();
387
388 if let Ok(auth_key) = self.get_key_by_pubkey(pubkey) {
390 results.push((SigKey::from_pubkey(pubkey), *auth_key.permissions()));
391 }
392
393 if let Some(global_perm) = self.get_global_permission() {
395 results.push((SigKey::global(pubkey), global_perm));
396 }
397
398 results.sort_by_key(|b| std::cmp::Reverse(b.1));
403 results
404 }
405
406 pub fn resolve_sig_key_for_operation(
410 &self,
411 pubkey: &PublicKey,
412 ) -> Result<(SigKey, Permission)> {
413 let matches = self.find_all_sigkeys_for_pubkey(pubkey);
414
415 matches.into_iter().next().ok_or_else(|| {
416 AuthError::PermissionDenied {
417 reason: format!("No active key found for pubkey: {pubkey}"),
418 }
419 .into()
420 })
421 }
422
423 pub fn can_modify_key(
427 &self,
428 signing_key: &ResolvedAuth,
429 target_pubkey: &PublicKey,
430 ) -> Result<bool> {
431 if !signing_key.effective_permission.can_admin() {
433 return Ok(false);
434 }
435
436 let target_key = self.get_key_by_pubkey(target_pubkey)?;
438
439 Ok(signing_key.effective_permission >= *target_key.permissions())
441 }
442
443 pub fn can_create_key(
445 &self,
446 signing_key: &ResolvedAuth,
447 new_key_permissions: &Permission,
448 ) -> Result<bool> {
449 if !signing_key.effective_permission.can_admin() {
451 return Ok(false);
452 }
453
454 Ok(signing_key.effective_permission >= *new_key_permissions)
456 }
457}
458
459impl Default for AuthSettings {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use crate::crdt::CRDT;
469
470 #[test]
471 fn test_auth_settings_basic_operations() {
472 let mut settings = AuthSettings::new();
473
474 let pubkey = PublicKey::random();
475 let auth_key = AuthKey::active(Some("laptop"), Permission::Write(10));
476
477 settings.add_key(&pubkey, auth_key.clone()).unwrap();
478
479 let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
481 assert_eq!(retrieved.name(), Some("laptop"));
482 assert_eq!(retrieved.permissions(), auth_key.permissions());
483 assert_eq!(retrieved.status(), auth_key.status());
484 }
485
486 #[test]
487 fn test_find_keys_by_name() {
488 let mut settings = AuthSettings::new();
489
490 let pubkey1 = PublicKey::random();
491 let pubkey2 = PublicKey::random();
492
493 settings
495 .add_key(
496 &pubkey1,
497 AuthKey::active(Some("device"), Permission::Write(10)),
498 )
499 .unwrap();
500 settings
501 .add_key(
502 &pubkey2,
503 AuthKey::active(Some("device"), Permission::Admin(1)),
504 )
505 .unwrap();
506
507 let matches = settings.find_keys_by_name("device");
509 assert_eq!(matches.len(), 2);
510 }
511
512 #[test]
513 fn test_revoke_key() {
514 let mut settings = AuthSettings::new();
515
516 let pubkey = PublicKey::random();
517 let auth_key = AuthKey::active(Some("laptop"), Permission::Admin(5));
518
519 settings.add_key(&pubkey, auth_key).unwrap();
520
521 settings.revoke_key(&pubkey).unwrap();
523
524 let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
526 assert_eq!(retrieved.status(), &KeyStatus::Revoked);
527 }
528
529 #[test]
530 fn test_global_permission() {
531 let mut settings = AuthSettings::new();
532
533 assert!(!settings.has_global_permission());
535 assert_eq!(settings.get_global_permission(), None);
536
537 let global_key = AuthKey::active(None, Permission::Write(10));
539 settings.set_global_permission(global_key);
540
541 assert!(settings.has_global_permission());
543 assert_eq!(
544 settings.get_global_permission(),
545 Some(Permission::Write(10))
546 );
547
548 assert!(settings.global_permission_grants_access(&Permission::Read));
550 assert!(settings.global_permission_grants_access(&Permission::Write(10)));
551 assert!(!settings.global_permission_grants_access(&Permission::Write(5)));
552 assert!(!settings.global_permission_grants_access(&Permission::Admin(10)));
553 }
554
555 #[test]
556 fn test_resolve_hint_pubkey() {
557 let mut settings = AuthSettings::new();
558
559 let pubkey = PublicKey::random();
560 settings
561 .add_key(
562 &pubkey,
563 AuthKey::active(Some("laptop"), Permission::Write(10)),
564 )
565 .unwrap();
566
567 let hint = KeyHint::from_pubkey(&pubkey);
569 let matches = settings.resolve_hint(&hint).unwrap();
570 assert_eq!(matches.len(), 1);
571 assert_eq!(matches[0].public_key, pubkey);
572 assert_eq!(matches[0].effective_permission, Permission::Write(10));
573 }
574
575 #[test]
576 fn test_resolve_hint_name() {
577 let mut settings = AuthSettings::new();
578
579 let pubkey = PublicKey::random();
580 settings
581 .add_key(
582 &pubkey,
583 AuthKey::active(Some("laptop"), Permission::Write(10)),
584 )
585 .unwrap();
586
587 let hint = KeyHint::from_name("laptop");
589 let matches = settings.resolve_hint(&hint).unwrap();
590 assert_eq!(matches.len(), 1);
591 assert_eq!(matches[0].public_key, pubkey);
592 assert_eq!(matches[0].effective_permission, Permission::Write(10));
593 }
594
595 #[test]
596 fn test_resolve_hint_global() {
597 let mut settings = AuthSettings::new();
598
599 settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
601
602 let actual_pubkey = PublicKey::random();
603 let hint = KeyHint::global(&actual_pubkey);
604 let matches = settings.resolve_hint(&hint).unwrap();
605
606 assert_eq!(matches.len(), 1);
607 assert_eq!(matches[0].public_key, actual_pubkey);
608 assert_eq!(matches[0].effective_permission, Permission::Write(10));
609 }
610
611 #[test]
612 fn test_find_all_sigkeys_for_pubkey() {
613 let mut settings = AuthSettings::new();
614
615 let pubkey = PublicKey::random();
616
617 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
619 assert_eq!(results.len(), 0);
620
621 settings
623 .add_key(
624 &pubkey,
625 AuthKey::active(Some("device1"), Permission::Write(5)),
626 )
627 .unwrap();
628
629 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
630 assert_eq!(results.len(), 1);
631
632 settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
634
635 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
636 assert_eq!(results.len(), 2);
637 }
638
639 #[test]
640 fn test_resolve_sig_key_for_operation() {
641 let mut settings = AuthSettings::new();
642
643 let pubkey = PublicKey::random();
644
645 let result = settings.resolve_sig_key_for_operation(&pubkey);
647 assert!(result.is_err());
648
649 settings
651 .add_key(
652 &pubkey,
653 AuthKey::active(Some("device"), Permission::Write(5)),
654 )
655 .unwrap();
656
657 let (sig_key, granted_perm) = settings.resolve_sig_key_for_operation(&pubkey).unwrap();
659 assert!(sig_key.has_pubkey_hint(&pubkey));
660 assert_eq!(granted_perm, Permission::Write(5));
661 }
662
663 #[test]
664 fn test_can_access() {
665 let mut settings = AuthSettings::new();
666
667 let pubkey = PublicKey::random();
668 let other_pubkey = PublicKey::random();
669
670 assert!(!settings.can_access(&pubkey, &Permission::Read));
672
673 settings
675 .add_key(
676 &pubkey,
677 AuthKey::active(Some("device"), Permission::Write(5)),
678 )
679 .unwrap();
680
681 assert!(settings.can_access(&pubkey, &Permission::Read));
683 assert!(settings.can_access(&pubkey, &Permission::Write(5)));
684 assert!(!settings.can_access(&pubkey, &Permission::Admin(1)));
685
686 assert!(!settings.can_access(&other_pubkey, &Permission::Read));
688
689 settings.set_global_permission(AuthKey::active(None, Permission::Read));
691
692 assert!(settings.can_access(&other_pubkey, &Permission::Read));
694 assert!(!settings.can_access(&other_pubkey, &Permission::Write(10)));
695 }
696
697 #[test]
698 fn test_auth_settings_merge() {
699 let mut settings1 = AuthSettings::new();
700 let mut settings2 = AuthSettings::new();
701
702 let pubkey1 = PublicKey::random();
703 let pubkey2 = PublicKey::random();
704
705 settings1
706 .add_key(
707 &pubkey1,
708 AuthKey::active(Some("key1"), Permission::Write(10)),
709 )
710 .unwrap();
711 settings2
712 .add_key(
713 &pubkey2,
714 AuthKey::active(Some("key2"), Permission::Admin(5)),
715 )
716 .unwrap();
717
718 let merged_doc = settings1.as_doc().merge(settings2.as_doc()).unwrap();
720 let merged_settings: AuthSettings = merged_doc.into();
721
722 assert!(merged_settings.get_key_by_pubkey(&pubkey1).is_ok());
724 assert!(merged_settings.get_key_by_pubkey(&pubkey2).is_ok());
725 }
726}