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 find_all_sigkeys_for_pubkey(&self, pubkey: &PublicKey) -> Vec<(SigKey, Permission)> {
361 let mut results = Vec::new();
362
363 if let Ok(auth_key) = self.get_key_by_pubkey(pubkey)
368 && auth_key.is_active()
369 {
370 results.push((SigKey::from_pubkey(pubkey), *auth_key.permissions()));
371 }
372
373 if let Some(global_perm) = self.get_global_permission() {
375 results.push((SigKey::global(pubkey), global_perm));
376 }
377
378 results.sort_by_key(|b| std::cmp::Reverse(b.1));
383 results
384 }
385
386 pub fn resolve_sig_key_for_operation(
390 &self,
391 pubkey: &PublicKey,
392 ) -> Result<(SigKey, Permission)> {
393 let matches = self.find_all_sigkeys_for_pubkey(pubkey);
394
395 matches.into_iter().next().ok_or_else(|| {
396 AuthError::PermissionDenied {
397 reason: format!("No active key found for pubkey: {pubkey}"),
398 }
399 .into()
400 })
401 }
402
403 pub fn can_modify_key(
407 &self,
408 signing_key: &ResolvedAuth,
409 target_pubkey: &PublicKey,
410 ) -> Result<bool> {
411 if !signing_key.effective_permission.can_admin() {
413 return Ok(false);
414 }
415
416 let target_key = self.get_key_by_pubkey(target_pubkey)?;
418
419 Ok(signing_key.effective_permission >= *target_key.permissions())
421 }
422
423 pub fn can_create_key(
425 &self,
426 signing_key: &ResolvedAuth,
427 new_key_permissions: &Permission,
428 ) -> Result<bool> {
429 if !signing_key.effective_permission.can_admin() {
431 return Ok(false);
432 }
433
434 Ok(signing_key.effective_permission >= *new_key_permissions)
436 }
437}
438
439impl Default for AuthSettings {
440 fn default() -> Self {
441 Self::new()
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use crate::crdt::CRDT;
449
450 #[test]
451 fn test_auth_settings_basic_operations() {
452 let mut settings = AuthSettings::new();
453
454 let pubkey = PublicKey::random();
455 let auth_key = AuthKey::active(Some("laptop"), Permission::Write(10));
456
457 settings.add_key(&pubkey, auth_key.clone()).unwrap();
458
459 let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
461 assert_eq!(retrieved.name(), Some("laptop"));
462 assert_eq!(retrieved.permissions(), auth_key.permissions());
463 assert_eq!(retrieved.status(), auth_key.status());
464 }
465
466 #[test]
467 fn test_find_keys_by_name() {
468 let mut settings = AuthSettings::new();
469
470 let pubkey1 = PublicKey::random();
471 let pubkey2 = PublicKey::random();
472
473 settings
475 .add_key(
476 &pubkey1,
477 AuthKey::active(Some("device"), Permission::Write(10)),
478 )
479 .unwrap();
480 settings
481 .add_key(
482 &pubkey2,
483 AuthKey::active(Some("device"), Permission::Admin(1)),
484 )
485 .unwrap();
486
487 let matches = settings.find_keys_by_name("device");
489 assert_eq!(matches.len(), 2);
490 }
491
492 #[test]
493 fn test_revoke_key() {
494 let mut settings = AuthSettings::new();
495
496 let pubkey = PublicKey::random();
497 let auth_key = AuthKey::active(Some("laptop"), Permission::Admin(5));
498
499 settings.add_key(&pubkey, auth_key).unwrap();
500
501 settings.revoke_key(&pubkey).unwrap();
503
504 let retrieved = settings.get_key_by_pubkey(&pubkey).unwrap();
506 assert_eq!(retrieved.status(), &KeyStatus::Revoked);
507 }
508
509 #[test]
510 fn test_global_permission() {
511 let mut settings = AuthSettings::new();
512
513 assert!(!settings.has_global_permission());
515 assert_eq!(settings.get_global_permission(), None);
516
517 let global_key = AuthKey::active(None, Permission::Write(10));
519 settings.set_global_permission(global_key);
520
521 assert!(settings.has_global_permission());
523 assert_eq!(
524 settings.get_global_permission(),
525 Some(Permission::Write(10))
526 );
527 }
528
529 #[test]
530 fn test_resolve_hint_pubkey() {
531 let mut settings = AuthSettings::new();
532
533 let pubkey = PublicKey::random();
534 settings
535 .add_key(
536 &pubkey,
537 AuthKey::active(Some("laptop"), Permission::Write(10)),
538 )
539 .unwrap();
540
541 let hint = KeyHint::from_pubkey(&pubkey);
543 let matches = settings.resolve_hint(&hint).unwrap();
544 assert_eq!(matches.len(), 1);
545 assert_eq!(matches[0].public_key, pubkey);
546 assert_eq!(matches[0].effective_permission, Permission::Write(10));
547 }
548
549 #[test]
550 fn test_resolve_hint_name() {
551 let mut settings = AuthSettings::new();
552
553 let pubkey = PublicKey::random();
554 settings
555 .add_key(
556 &pubkey,
557 AuthKey::active(Some("laptop"), Permission::Write(10)),
558 )
559 .unwrap();
560
561 let hint = KeyHint::from_name("laptop");
563 let matches = settings.resolve_hint(&hint).unwrap();
564 assert_eq!(matches.len(), 1);
565 assert_eq!(matches[0].public_key, pubkey);
566 assert_eq!(matches[0].effective_permission, Permission::Write(10));
567 }
568
569 #[test]
570 fn test_resolve_hint_global() {
571 let mut settings = AuthSettings::new();
572
573 settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
575
576 let actual_pubkey = PublicKey::random();
577 let hint = KeyHint::global(&actual_pubkey);
578 let matches = settings.resolve_hint(&hint).unwrap();
579
580 assert_eq!(matches.len(), 1);
581 assert_eq!(matches[0].public_key, actual_pubkey);
582 assert_eq!(matches[0].effective_permission, Permission::Write(10));
583 }
584
585 #[test]
586 fn test_find_all_sigkeys_for_pubkey() {
587 let mut settings = AuthSettings::new();
588
589 let pubkey = PublicKey::random();
590
591 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
593 assert_eq!(results.len(), 0);
594
595 settings
597 .add_key(
598 &pubkey,
599 AuthKey::active(Some("device1"), Permission::Write(5)),
600 )
601 .unwrap();
602
603 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
604 assert_eq!(results.len(), 1);
605
606 settings.set_global_permission(AuthKey::active(None, Permission::Write(10)));
608
609 let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
610 assert_eq!(results.len(), 2);
611
612 let mut revoked_settings = AuthSettings::new();
616 let revoked_pubkey = PublicKey::random();
617 revoked_settings
618 .add_key(
619 &revoked_pubkey,
620 AuthKey::new(Some("revoked"), Permission::Admin(0), KeyStatus::Revoked),
621 )
622 .unwrap();
623 assert!(
624 revoked_settings
625 .find_all_sigkeys_for_pubkey(&revoked_pubkey)
626 .is_empty(),
627 "revoked direct key should not be offered as an access method"
628 );
629 }
630
631 #[test]
632 fn test_resolve_sig_key_for_operation() {
633 let mut settings = AuthSettings::new();
634
635 let pubkey = PublicKey::random();
636
637 let result = settings.resolve_sig_key_for_operation(&pubkey);
639 assert!(result.is_err());
640
641 settings
643 .add_key(
644 &pubkey,
645 AuthKey::active(Some("device"), Permission::Write(5)),
646 )
647 .unwrap();
648
649 let (sig_key, granted_perm) = settings.resolve_sig_key_for_operation(&pubkey).unwrap();
651 assert!(sig_key.has_pubkey_hint(&pubkey));
652 assert_eq!(granted_perm, Permission::Write(5));
653 }
654
655 #[test]
656 fn test_auth_settings_merge() {
657 let mut settings1 = AuthSettings::new();
658 let mut settings2 = AuthSettings::new();
659
660 let pubkey1 = PublicKey::random();
661 let pubkey2 = PublicKey::random();
662
663 settings1
664 .add_key(
665 &pubkey1,
666 AuthKey::active(Some("key1"), Permission::Write(10)),
667 )
668 .unwrap();
669 settings2
670 .add_key(
671 &pubkey2,
672 AuthKey::active(Some("key2"), Permission::Admin(5)),
673 )
674 .unwrap();
675
676 let merged_doc = settings1.as_doc().merge(settings2.as_doc()).unwrap();
678 let merged_settings: AuthSettings = merged_doc.into();
679
680 assert!(merged_settings.get_key_by_pubkey(&pubkey1).is_ok());
682 assert!(merged_settings.get_key_by_pubkey(&pubkey2).is_ok());
683 }
684}