Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica
Skip to main content

eidetica/auth/
settings.rs

1//! Authentication settings management for Eidetica
2//!
3//! This module provides a wrapper around Doc for managing authentication
4//! settings. Keys are indexed by pubkey to prevent collision bugs.
5//! Names are optional metadata that can be used as hints in signatures.
6
7use 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/// Authentication settings view/interface over Doc data
23///
24/// Keys are stored by pubkey in the "keys" sub-object.
25/// Delegations are stored by root tree ID in the "delegations" sub-object.
26/// Global permission is stored in the "global" sub-object.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AuthSettings {
29    /// Doc data from _settings.auth - this is a view, not the authoritative copy
30    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    /// Create a new empty auth settings view
47    pub fn new() -> Self {
48        Self { inner: Doc::new() }
49    }
50
51    /// Get the underlying Doc for direct access
52    pub fn as_doc(&self) -> &Doc {
53        &self.inner
54    }
55
56    /// Get mutable access to the underlying Doc
57    pub fn as_doc_mut(&mut self) -> &mut Doc {
58        &mut self.inner
59    }
60
61    // ==================== Key Operations ====================
62
63    /// Add a new authentication key by pubkey (fails if key already exists)
64    ///
65    /// # Arguments
66    /// * `pubkey` - The public key
67    /// * `key` - The AuthKey containing permissions, status, and optional name
68    pub fn add_key(&mut self, pubkey: &PublicKey, key: AuthKey) -> Result<()> {
69        let pubkey_str = pubkey.to_string();
70
71        // Check if key already exists
72        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    /// Explicitly overwrite an existing authentication key
84    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    /// Get a key by its public key
91    pub fn get_key_by_pubkey(&self, pubkey: &PublicKey) -> Result<AuthKey> {
92        self.get_key_by_str(&pubkey.to_string())
93    }
94
95    /// Get a key by its public key string
96    ///
97    /// Internal helper used by `get_key_by_pubkey` and `resolve_hint` (which
98    /// already has strings from Doc storage).
99    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    /// Find keys by name (may return multiple if names collide)
119    ///
120    /// Returns Vec of (pubkey, AuthKey) tuples sorted by pubkey for deterministic ordering.
121    pub fn find_keys_by_name(&self, name: &str) -> Vec<(String, AuthKey)> {
122        let mut matches = Vec::new();
123
124        // Get all keys and filter by name
125        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        // Sort by pubkey for deterministic ordering
134        matches.sort_by(|a, b| a.0.cmp(&b.0));
135        matches
136    }
137
138    /// Get all authentication keys
139    pub fn get_all_keys(&self) -> Result<HashMap<String, AuthKey>> {
140        let mut result: HashMap<String, AuthKey> = HashMap::new();
141
142        // Get the "keys" sub-doc
143        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    /// Rename a key by pubkey
157    ///
158    /// Updates only the display name of an existing key, preserving its
159    /// permissions and status.
160    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    /// Revoke a key by pubkey
169    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    // ==================== Delegation Operations ====================
178
179    /// Add or update a delegated tree reference
180    ///
181    /// The delegation is stored by root tree ID, extracted from `tree_ref.tree.root`.
182    /// This ensures collision-resistant storage similar to key storage by pubkey.
183    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    /// Get a delegated tree reference by root tree ID
190    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    /// Get all delegated tree references
210    ///
211    /// Returns a map from root tree ID to the delegation reference.
212    pub fn get_all_delegated_trees(&self) -> Result<HashMap<ID, DelegatedTreeRef>> {
213        let mut result: HashMap<ID, DelegatedTreeRef> = HashMap::new();
214
215        // Get the "delegations" sub-doc
216        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    // ==================== Global Permission ====================
231
232    /// Set the global permission
233    ///
234    /// Stores the global permission at the `global` path, separate from
235    /// individual key entries in the `keys` namespace.
236    pub fn set_global_permission(&mut self, key: AuthKey) {
237        self.inner.set("global", key);
238    }
239
240    /// Get the global permission AuthKey
241    ///
242    /// Reads from the `global` path.
243    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    // ==================== Key Hint Resolution ====================
263
264    /// Resolve a key hint to matching authentication info
265    ///
266    /// Returns Vec of ResolvedAuth. For pubkey hints, returns at most one.
267    /// For name hints, may return multiple if names collide. Caller should try each
268    /// until signature verifies.
269    ///
270    /// # Name Collision Handling
271    ///
272    /// When multiple keys share the same name, all matching keys are returned.
273    /// The caller (typically `validate_entry`) should iterate through the matches
274    /// and attempt signature verification with each until one succeeds.
275    pub fn resolve_hint(&self, hint: &KeyHint) -> Result<Vec<ResolvedAuth>> {
276        // Handle global permission
277        if hint.is_global() {
278            // Global hint - check that global permission exists
279            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            // The actual pubkey is directly in hint.pubkey
286            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 ResolvedAuth with actual pubkey and global permission
294            // There is only 1 global, no need to look for others
295            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        // Direct pubkey lookup
303        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        // Name lookup - may return multiple matches
315        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            // Convert all matches to ResolvedAuth
324            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        // No hint set - empty/unsigned
336        Ok(vec![])
337    }
338
339    // ==================== Permission Helpers ====================
340
341    /// Check if global permission exists and is active
342    pub fn has_global_permission(&self) -> bool {
343        self.get_global_permission().is_some()
344    }
345
346    /// Get global permission level if it exists and is active
347    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    /// Check if global permission grants sufficient access
358    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    // ==================== Access Control ====================
367
368    /// Check if a public key can access the database with the requested permission
369    pub fn can_access(&self, pubkey: &PublicKey, requested_permission: &Permission) -> bool {
370        // First check if there's a specific key entry for this pubkey
371        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        // Check global permission
379        self.global_permission_grants_access(requested_permission)
380    }
381
382    /// Find all SigKeys that a public key can use to access this database
383    ///
384    /// Returns (SigKey, Permission) tuples sorted by permission (highest first)
385    pub fn find_all_sigkeys_for_pubkey(&self, pubkey: &PublicKey) -> Vec<(SigKey, Permission)> {
386        let mut results = Vec::new();
387
388        // Check if this pubkey has a direct key entry
389        if let Ok(auth_key) = self.get_key_by_pubkey(pubkey) {
390            results.push((SigKey::from_pubkey(pubkey), *auth_key.permissions()));
391        }
392
393        // Check if global permission exists
394        if let Some(global_perm) = self.get_global_permission() {
395            results.push((SigKey::global(pubkey), global_perm));
396        }
397
398        // Note: Delegation path discovery happens at the Database::find_sigkeys() level
399        // because it requires async Instance access to load delegated tree auth settings.
400
401        // Sort by permission, highest first (reverse sort since Permission Ord has higher > lower)
402        results.sort_by_key(|b| std::cmp::Reverse(b.1));
403        results
404    }
405
406    /// Resolve which SigKey should be used for an operation
407    ///
408    /// Returns the SigKey with highest permission for the given pubkey.
409    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    // ==================== Key Modification Authorization ====================
424
425    /// Check if a signing key can modify an existing target key
426    pub fn can_modify_key(
427        &self,
428        signing_key: &ResolvedAuth,
429        target_pubkey: &PublicKey,
430    ) -> Result<bool> {
431        // Must have admin permissions to modify keys
432        if !signing_key.effective_permission.can_admin() {
433            return Ok(false);
434        }
435
436        // Get target key info
437        let target_key = self.get_key_by_pubkey(target_pubkey)?;
438
439        // Signing key must be >= target key permissions
440        Ok(signing_key.effective_permission >= *target_key.permissions())
441    }
442
443    /// Check if a signing key can create a new key with the specified permissions
444    pub fn can_create_key(
445        &self,
446        signing_key: &ResolvedAuth,
447        new_key_permissions: &Permission,
448    ) -> Result<bool> {
449        // Must have admin permissions to create keys
450        if !signing_key.effective_permission.can_admin() {
451            return Ok(false);
452        }
453
454        // Signing key must be >= new key permissions
455        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        // Retrieve the key
480        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        // Add two keys with same name
494        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        // Find by name should return both
508        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        // Revoke the key
522        settings.revoke_key(&pubkey).unwrap();
523
524        // Check that it's revoked
525        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        // No global permission initially
534        assert!(!settings.has_global_permission());
535        assert_eq!(settings.get_global_permission(), None);
536
537        // Set global Write(10) permission
538        let global_key = AuthKey::active(None, Permission::Write(10));
539        settings.set_global_permission(global_key);
540
541        // Global permission should now be detected
542        assert!(settings.has_global_permission());
543        assert_eq!(
544            settings.get_global_permission(),
545            Some(Permission::Write(10))
546        );
547
548        // Test permission granting
549        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        // Resolve by pubkey hint
568        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        // Resolve by name hint
588        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        // Set global permission
600        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        // No keys - should return empty vec
618        let results = settings.find_all_sigkeys_for_pubkey(&pubkey);
619        assert_eq!(results.len(), 0);
620
621        // Add direct key
622        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        // Set global permission
633        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        // No keys configured - should fail
646        let result = settings.resolve_sig_key_for_operation(&pubkey);
647        assert!(result.is_err());
648
649        // Add device key
650        settings
651            .add_key(
652                &pubkey,
653                AuthKey::active(Some("device"), Permission::Write(5)),
654            )
655            .unwrap();
656
657        // Should resolve to direct pubkey
658        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        // No access without keys
671        assert!(!settings.can_access(&pubkey, &Permission::Read));
672
673        // Add specific key
674        settings
675            .add_key(
676                &pubkey,
677                AuthKey::active(Some("device"), Permission::Write(5)),
678            )
679            .unwrap();
680
681        // Specific key should have access
682        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        // Other key should not have access
687        assert!(!settings.can_access(&other_pubkey, &Permission::Read));
688
689        // Set global permission
690        settings.set_global_permission(AuthKey::active(None, Permission::Read));
691
692        // Other key should now have read access via global
693        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        // Merge at Doc level
719        let merged_doc = settings1.as_doc().merge(settings2.as_doc()).unwrap();
720        let merged_settings: AuthSettings = merged_doc.into();
721
722        // Both keys should be present
723        assert!(merged_settings.get_key_by_pubkey(&pubkey1).is_ok());
724        assert!(merged_settings.get_key_by_pubkey(&pubkey2).is_ok());
725    }
726}