1 //! Authorization Services support.
2 
3 /// # Potential improvements
4 ///
5 /// * When generic specialization stabilizes prevent copying from CString
6 ///   arguments.
7 /// * AuthorizationCopyRightsAsync
8 /// * Provide constants for well known item names
9 use crate::base::{Error, Result};
10 use core_foundation::base::{CFTypeRef, TCFType};
11 use core_foundation::bundle::CFBundleRef;
12 use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
13 use core_foundation::string::{CFString, CFStringRef};
14 use security_framework_sys::authorization as sys;
15 use security_framework_sys::base::errSecConversionError;
16 use std::mem::MaybeUninit;
17 use std::os::raw::c_void;
18 use std::{
19     convert::TryFrom,
20     ffi::{CStr, CString},
21 };
22 use std::{convert::TryInto, marker::PhantomData};
23 use sys::AuthorizationExternalForm;
24 
25 macro_rules! optional_str_to_cfref {
26     ($string:ident) => {{
27         $string
28             .map(CFString::new)
29             .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
30     }};
31 }
32 
33 macro_rules! cstring_or_err {
34     ($x:expr) => {{
35         CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
36     }};
37 }
38 
39 bitflags::bitflags! {
40     /// The flags used to specify authorization options.
41     pub struct Flags: sys::AuthorizationFlags {
42         /// An empty flag set that you use as a placeholder when you don't want
43         /// any of the other flags.
44         const DEFAULTS = sys::kAuthorizationFlagDefaults;
45 
46         /// A flag that permits user interaction as needed.
47         const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
48 
49         /// A flag that permits the Security Server to attempt to grant the
50         /// rights requested.
51         const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
52 
53         /// A flag that permits the Security Server to grant rights on an
54         /// individual basis.
55         const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
56 
57         /// A flag that instructs the Security Server to revoke authorization.
58         const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
59 
60         /// A flag that instructs the Security Server to preauthorize the rights
61         /// requested.
62         const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
63     }
64 }
65 
66 impl Default for Flags {
67     #[inline(always)]
default() -> Flags68     fn default() -> Flags {
69         Flags::DEFAULTS
70     }
71 }
72 
73 /// Information about an authorization right or the environment.
74 #[repr(C)]
75 pub struct AuthorizationItem(sys::AuthorizationItem);
76 
77 impl AuthorizationItem {
78     /// The required name of the authorization right or environment data.
79     ///
80     /// If `name` isn't convertable to a `CString` it will return
81     /// Err(errSecConversionError).
name(&self) -> &str82     pub fn name(&self) -> &str {
83         unsafe {
84             CStr::from_ptr(self.0.name)
85                 .to_str()
86                 .expect("AuthorizationItem::name failed to convert &str to CStr")
87         }
88     }
89 
90     /// The information pertaining to the name field. Do not rely on NULL
91     /// termination of string data.
value(&self) -> Option<&[u8]>92     pub fn value(&self) -> Option<&[u8]> {
93         if self.0.value.is_null() {
94             return None;
95         }
96 
97         let value =
98             unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) };
99 
100         Some(value)
101     }
102 }
103 
104 /// A set of authorization items returned and owned by the Security Server.
105 #[derive(Debug)]
106 #[repr(C)]
107 pub struct AuthorizationItemSet<'a> {
108     inner: *const sys::AuthorizationItemSet,
109     phantom: PhantomData<&'a sys::AuthorizationItemSet>,
110 }
111 
112 impl<'a> Drop for AuthorizationItemSet<'a> {
113     #[inline]
drop(&mut self)114     fn drop(&mut self) {
115         unsafe {
116             sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet);
117         }
118     }
119 }
120 
121 /// Used by `AuthorizationItemSetBuilder` to store data pointed to by
122 /// `sys::AuthorizationItemSet`.
123 #[derive(Debug)]
124 pub struct AuthorizationItemSetStorage {
125     /// The layout of this is a little awkward because of the requirements of
126     /// Apple's APIs. `items` contains pointers to data owned by `names` and
127     /// `values`, so we must not modify them once `items` has been set up.
128     names: Vec<CString>,
129     values: Vec<Option<Vec<u8>>>,
130     items: Vec<sys::AuthorizationItem>,
131 
132     /// Must not be given to APIs which would attempt to modify it.
133     ///
134     /// See `AuthorizationItemSet` for sets owned by the Security Server which
135     /// are writable.
136     pub set: sys::AuthorizationItemSet,
137 }
138 
139 impl Default for AuthorizationItemSetStorage {
140     #[inline]
default() -> Self141     fn default() -> Self {
142         AuthorizationItemSetStorage {
143             names: Vec::new(),
144             values: Vec::new(),
145             items: Vec::new(),
146             set: sys::AuthorizationItemSet {
147                 count: 0,
148                 items: std::ptr::null_mut(),
149             },
150         }
151     }
152 }
153 
154 /// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use
155 /// rust types. All names and values passed in will be copied.
156 #[derive(Debug, Default)]
157 pub struct AuthorizationItemSetBuilder {
158     storage: AuthorizationItemSetStorage,
159 }
160 
161 // Stores AuthorizationItems contiguously, and their items separately
162 impl AuthorizationItemSetBuilder {
163     /// Creates a new `AuthorizationItemSetStore`, which simplifies creating
164     /// owned vectors of `AuthorizationItem`s.
165     #[inline(always)]
new() -> AuthorizationItemSetBuilder166     pub fn new() -> AuthorizationItemSetBuilder {
167         Default::default()
168     }
169 
170     /// Adds an AuthorizationItem with the name set to a right and an empty
171     /// value.
172     ///
173     /// If `name` isn't convertable to a `CString` it will return
174     /// Err(errSecConversionError).
add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self>175     pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> {
176         self.storage.names.push(cstring_or_err!(name)?);
177         self.storage.values.push(None);
178         Ok(self)
179     }
180 
181     /// Adds an AuthorizationItem with arbitrary data.
182     ///
183     /// If `name` isn't convertable to a `CString` it will return
184     /// Err(errSecConversionError).
add_data<N, V>(mut self, name: N, value: V) -> Result<Self> where N: Into<Vec<u8>>, V: Into<Vec<u8>>,185     pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self>
186     where
187         N: Into<Vec<u8>>,
188         V: Into<Vec<u8>>,
189     {
190         self.storage.names.push(cstring_or_err!(name)?);
191         self.storage.values.push(Some(value.into()));
192         Ok(self)
193     }
194 
195     /// Adds an AuthorizationItem with NULL terminated string data.
196     ///
197     /// If `name` or `value` isn't convertable to a `CString` it will return
198     /// Err(errSecConversionError).
add_string<N, V>(mut self, name: N, value: V) -> Result<Self> where N: Into<Vec<u8>>, V: Into<Vec<u8>>,199     pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self>
200     where
201         N: Into<Vec<u8>>,
202         V: Into<Vec<u8>>,
203     {
204         self.storage.names.push(cstring_or_err!(name)?);
205         self.storage
206             .values
207             .push(Some(cstring_or_err!(value)?.to_bytes().to_vec()));
208         Ok(self)
209     }
210 
211     /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the
212     /// data it points to.
build(mut self) -> AuthorizationItemSetStorage213     pub fn build(mut self) -> AuthorizationItemSetStorage {
214         self.storage.items = self
215             .storage
216             .names
217             .iter()
218             .zip(self.storage.values.iter())
219             .map(|(n, v)| sys::AuthorizationItem {
220                 name: n.as_ptr(),
221                 value: v
222                     .as_ref()
223                     .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void),
224                 valueLength: v.as_ref().map_or(0, |v| v.len()),
225                 flags: 0,
226             })
227             .collect();
228 
229         self.storage.set = sys::AuthorizationItemSet {
230             count: self.storage.items.len() as u32,
231             items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem,
232         };
233 
234         self.storage
235     }
236 }
237 
238 /// Used by `Authorization::set_item` to define the rules of he right.
239 pub enum RightDefinition<'a> {
240     /// The dictionary will contain the keys and values that define the rules.
241     FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>),
242 
243     /// The specified right's rules will be duplicated.
244     FromExistingRight(&'a str),
245 }
246 
247 /// A wrapper around AuthorizationCreate and functions which operate on an
248 /// AuthorizationRef.
249 #[derive(Debug)]
250 pub struct Authorization {
251     handle: sys::AuthorizationRef,
252     free_flags: Flags,
253 }
254 
255 impl TryFrom<AuthorizationExternalForm> for Authorization {
256     type Error = Error;
257 
258     /// Internalizes the external representation of an authorization reference.
259     #[cold]
try_from(external_form: AuthorizationExternalForm) -> Result<Self>260     fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> {
261         let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
262 
263         let status = unsafe {
264             sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr())
265         };
266 
267         if status != sys::errAuthorizationSuccess {
268             return Err(Error::from_code(status));
269         }
270 
271         let auth = Authorization {
272             handle: unsafe { handle.assume_init() },
273             free_flags: Default::default(),
274         };
275 
276         Ok(auth)
277     }
278 }
279 
280 impl<'a> Authorization {
281     /// Creates an authorization object which has no environment or associated
282     /// rights.
283     #[inline]
default() -> Result<Self>284     pub fn default() -> Result<Self> {
285         Self::new(None, None, Default::default())
286     }
287 
288     /// Creates an authorization reference and provides an option to authorize
289     /// or preauthorize rights.
290     ///
291     /// `rights` should be the names of the rights you want to create.
292     ///
293     /// `environment` is used when authorizing or preauthorizing rights. Not
294     /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass
295     /// icon or prompt data to be used in the authentication dialog box. In
296     /// macOS 10.4 and later, you can also pass a user name and password in
297     /// order to authorize a user without user interaction.
new( rights: Option<AuthorizationItemSetStorage>, environment: Option<AuthorizationItemSetStorage>, flags: Flags, ) -> Result<Self>298     pub fn new(
299         rights: Option<AuthorizationItemSetStorage>,
300         environment: Option<AuthorizationItemSetStorage>,
301         flags: Flags,
302     ) -> Result<Self> {
303         let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
304             &r.set as *const sys::AuthorizationItemSet
305         });
306 
307         let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
308             &e.set as *const sys::AuthorizationItemSet
309         });
310 
311         let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
312 
313         let status = unsafe {
314             sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
315         };
316 
317         if status != sys::errAuthorizationSuccess {
318             return Err(Error::from_code(status));
319         }
320 
321         Ok(Authorization {
322             handle: unsafe { handle.assume_init() },
323             free_flags: Default::default(),
324         })
325     }
326 
327     /// Internalizes the external representation of an authorization reference.
328     #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self>329     pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
330         external_form.try_into()
331     }
332 
333     /// By default the rights acquired will be retained by the Security Server.
334     /// Use this to ensure they are destroyed and to prevent shared rights'
335     /// continued used by other processes.
336     #[inline(always)]
destroy_rights(mut self)337     pub fn destroy_rights(mut self) {
338         self.free_flags = Flags::DESTROY_RIGHTS;
339     }
340 
341     /// Retrieve's the right's definition as a dictionary. Use `right_exists`
342     /// if you want to avoid retrieving the dictionary.
343     ///
344     /// `name` can be a wildcard right name.
345     ///
346     /// If `name` isn't convertable to a `CString` it will return
347     /// Err(errSecConversionError).
get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>>348     pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
349         let name = cstring_or_err!(name)?;
350         let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
351 
352         let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
353 
354         if status != sys::errAuthorizationSuccess {
355             return Err(Error::from_code(status));
356         }
357 
358         let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
359 
360         Ok(dict)
361     }
362 
363     /// Checks if a right exists within the policy database. This is the same as
364     /// `get_right`, but avoids a dictionary allocation.
365     ///
366     /// If `name` isn't convertable to a `CString` it will return
367     /// Err(errSecConversionError).
right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool>368     pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
369         let name = cstring_or_err!(name)?;
370 
371         let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
372 
373         Ok(status == sys::errAuthorizationSuccess)
374     }
375 
376     /// Removes a right from the policy database.
377     ///
378     /// `name` cannot be a wildcard right name.
379     ///
380     /// If `name` isn't convertable to a `CString` it will return
381     /// Err(errSecConversionError).
remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()>382     pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
383         let name = cstring_or_err!(name)?;
384 
385         let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
386 
387         if status != sys::errAuthorizationSuccess {
388             return Err(Error::from_code(status));
389         }
390 
391         Ok(())
392     }
393 
394     /// Creates or updates a right entry in the policy database. Your process
395     /// must have a code signature in order to be able to add rights to the
396     /// authorization database.
397     ///
398     /// `name` cannot be a wildcard right.
399     ///
400     /// `definition` can be either a `CFDictionaryRef` containing keys defining
401     /// the rules or a `CFStringRef` representing the name of another right
402     /// whose rules you wish to duplicaate.
403     ///
404     /// `description` is a key which can be used to look up localized
405     /// descriptions.
406     ///
407     /// `bundle` will be used to get localizations from if not the main bundle.
408     ///
409     /// `localeTableName` will be used to get localizations if provided.
410     ///
411     /// If `name` isn't convertable to a `CString` it will return
412     /// Err(errSecConversionError).
set_right<T: Into<Vec<u8>>>( &self, name: T, definition: RightDefinition<'_>, description: Option<&str>, bundle: Option<CFBundleRef>, locale: Option<&str>, ) -> Result<()>413     pub fn set_right<T: Into<Vec<u8>>>(
414         &self,
415         name: T,
416         definition: RightDefinition<'_>,
417         description: Option<&str>,
418         bundle: Option<CFBundleRef>,
419         locale: Option<&str>,
420     ) -> Result<()> {
421         let name = cstring_or_err!(name)?;
422 
423         let definition_cfstring: CFString;
424         let definition_ref = match definition {
425             RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
426             RightDefinition::FromExistingRight(def) => {
427                 definition_cfstring = CFString::new(def);
428                 definition_cfstring.as_CFTypeRef()
429             }
430         };
431 
432         let status = unsafe {
433             sys::AuthorizationRightSet(
434                 self.handle,
435                 name.as_ptr(),
436                 definition_ref,
437                 optional_str_to_cfref!(description),
438                 bundle.unwrap_or(std::ptr::null_mut()),
439                 optional_str_to_cfref!(locale),
440             )
441         };
442 
443         if status != sys::errAuthorizationSuccess {
444             return Err(Error::from_code(status));
445         }
446 
447         Ok(())
448     }
449 
450     /// An authorization plugin can store the results of an authentication
451     /// operation by calling the `SetContextValue` function. You can then
452     /// retrieve this supporting data, such as the user name.
453     ///
454     /// `tag` should specify the type of data the Security Server should return.
455     /// If `None`, all available information is retreieved.
456     ///
457     /// If `tag` isn't convertable to a `CString` it will return
458     /// Err(errSecConversionError).
copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>>459     pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
460         let tag_with_nul: CString;
461 
462         let tag_ptr = match tag {
463             Some(tag) => {
464                 tag_with_nul = cstring_or_err!(tag)?;
465                 tag_with_nul.as_ptr()
466             }
467             None => std::ptr::null(),
468         };
469 
470         let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
471 
472         let status =
473             unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
474 
475         if status != sys::errAuthorizationSuccess {
476             return Err(Error::from(status));
477         }
478 
479         let set = AuthorizationItemSet {
480             inner: unsafe { inner.assume_init() },
481             phantom: PhantomData,
482         };
483 
484         Ok(set)
485     }
486 
487     /// Creates an external representation of an authorization reference so that
488     /// you can transmit it between processes.
make_external_form(&self) -> Result<sys::AuthorizationExternalForm>489     pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
490         let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
491 
492         let status =
493             unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
494 
495         if status != sys::errAuthorizationSuccess {
496             return Err(Error::from(status));
497         }
498 
499         Ok(unsafe { external_form.assume_init() })
500     }
501 }
502 
503 impl Drop for Authorization {
504     #[inline]
drop(&mut self)505     fn drop(&mut self) {
506         unsafe {
507             sys::AuthorizationFree(self.handle, self.free_flags.bits());
508         }
509     }
510 }
511 
512 #[cfg(test)]
513 mod tests {
514     use super::*;
515     use core_foundation::string::CFString;
516 
517     #[test]
test_create_default_authorization()518     fn test_create_default_authorization() {
519         Authorization::default().unwrap();
520     }
521 
522     #[test]
test_create_allowed_authorization() -> Result<()>523     fn test_create_allowed_authorization() -> Result<()> {
524         let rights = AuthorizationItemSetBuilder::new()
525             .add_right("system.hdd.smart")?
526             .add_right("system.login.done")?
527             .build();
528 
529         Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
530 
531         Ok(())
532     }
533 
534     #[test]
test_create_then_destroy_allowed_authorization() -> Result<()>535     fn test_create_then_destroy_allowed_authorization() -> Result<()> {
536         let rights = AuthorizationItemSetBuilder::new()
537             .add_right("system.hdd.smart")?
538             .add_right("system.login.done")?
539             .build();
540 
541         let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
542         auth.destroy_rights();
543 
544         Ok(())
545     }
546 
547     #[test]
test_create_authorization_requiring_interaction() -> Result<()>548     fn test_create_authorization_requiring_interaction() -> Result<()> {
549         let rights = AuthorizationItemSetBuilder::new()
550             .add_right("system.privilege.admin")?
551             .build();
552 
553         let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
554 
555         assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
556 
557         Ok(())
558     }
559 
create_credentials_env() -> Result<AuthorizationItemSetStorage>560     fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
561         let set = AuthorizationItemSetBuilder::new()
562             .add_string(
563                 "username",
564                 option_env!("USER").expect("You must set the USER environment variable"),
565             )?
566             .add_string(
567                 "password",
568                 option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"),
569             )?
570             .build();
571 
572         Ok(set)
573     }
574 
575     #[test]
test_create_authorization_with_bad_credentials() -> Result<()>576     fn test_create_authorization_with_bad_credentials() -> Result<()> {
577         let rights = AuthorizationItemSetBuilder::new()
578             .add_right("system.privilege.admin")?
579             .build();
580 
581         let env = AuthorizationItemSetBuilder::new()
582             .add_string("username", "Tim Apple")?
583             .add_string("password", "butterfly")?
584             .build();
585 
586         let error =
587             Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
588 
589         assert_eq!(error.code(), sys::errAuthorizationDenied);
590 
591         Ok(())
592     }
593 
594     #[test]
595     #[ignore]
test_create_authorization_with_credentials() -> Result<()>596     fn test_create_authorization_with_credentials() -> Result<()> {
597         let rights = AuthorizationItemSetBuilder::new()
598             .add_right("system.privilege.admin")?
599             .build();
600 
601         let env = create_credentials_env()?;
602 
603         Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
604 
605         Ok(())
606     }
607 
608     #[test]
test_query_authorization_database() -> Result<()>609     fn test_query_authorization_database() -> Result<()> {
610         assert!(Authorization::right_exists("system.hdd.smart")?);
611         assert!(!Authorization::right_exists("EMPTY")?);
612 
613         let dict = Authorization::get_right("system.hdd.smart").unwrap();
614 
615         let key = CFString::from_static_string("class");
616         assert!(dict.contains_key(&key));
617 
618         let invalid_key = CFString::from_static_string("EMPTY");
619         assert!(!dict.contains_key(&invalid_key));
620 
621         Ok(())
622     }
623 
624     /// This test will only pass if its process has a valid code signature.
625     #[test]
626     #[ignore]
test_modify_authorization_database() -> Result<()>627     fn test_modify_authorization_database() -> Result<()> {
628         let rights = AuthorizationItemSetBuilder::new()
629             .add_right("config.modify.")?
630             .build();
631 
632         let env = create_credentials_env()?;
633 
634         let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
635 
636         assert!(!Authorization::right_exists("TEST_RIGHT")?);
637 
638         auth.set_right(
639             "TEST_RIGHT",
640             RightDefinition::FromExistingRight("system.hdd.smart"),
641             None,
642             None,
643             None,
644         )
645         .unwrap();
646 
647         assert!(Authorization::right_exists("TEST_RIGHT")?);
648 
649         auth.remove_right("TEST_RIGHT").unwrap();
650 
651         assert!(!Authorization::right_exists("TEST_RIGHT")?);
652 
653         Ok(())
654     }
655 }
656