apple_security_framework/
authorization.rs

1//! Authorization Services support.
2
3use std::{
4    convert::{TryFrom, TryInto},
5    ffi::{CStr, CString},
6    fs::File,
7    marker::PhantomData,
8    mem::MaybeUninit,
9    os::raw::c_void,
10    ptr::addr_of,
11};
12
13#[cfg(all(target_os = "macos", feature = "job-bless"))]
14use core_foundation::{
15    base::Boolean,
16    error::{CFError, CFErrorRef},
17};
18use core_foundation::{
19    base::{CFTypeRef, TCFType},
20    bundle::CFBundleRef,
21    dictionary::{CFDictionary, CFDictionaryRef},
22    string::{CFString, CFStringRef},
23};
24use security_framework_sys::{authorization as sys, base::errSecConversionError};
25use sys::AuthorizationExternalForm;
26
27/// # Potential improvements
28///
29/// * When generic specialization stabilizes prevent copying from `CString`
30///   arguments.
31/// * `AuthorizationCopyRightsAsync`
32/// * Provide constants for well known item names
33use crate::base::Error;
34
35/// # Potential improvements
36///
37/// * When generic specialization stabilizes prevent copying from `CString`
38///   arguments.
39/// * `AuthorizationCopyRightsAsync`
40/// * Provide constants for well known item names
41use crate::base::Result;
42
43macro_rules! optional_str_to_cfref {
44    ($string:ident) => {{
45        $string
46            .map(CFString::new)
47            .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
48    }};
49}
50
51macro_rules! cstring_or_err {
52    ($x:expr) => {{
53        CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
54    }};
55}
56
57bitflags::bitflags! {
58    /// The flags used to specify authorization options.
59    #[derive(Debug, Clone)]
60    pub struct Flags: sys::AuthorizationFlags {
61        /// An empty flag set that you use as a placeholder when you don't want
62        /// any of the other flags.
63        const DEFAULTS = sys::kAuthorizationFlagDefaults;
64
65        /// A flag that permits user interaction as needed.
66        const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
67
68        /// A flag that permits the Security Server to attempt to grant the
69        /// rights requested.
70        const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
71
72        /// A flag that permits the Security Server to grant rights on an
73        /// individual basis.
74        const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
75
76        /// A flag that instructs the Security Server to revoke authorization.
77        const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
78
79        /// A flag that instructs the Security Server to preauthorize the rights
80        /// requested.
81        const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
82    }
83}
84
85impl Default for Flags {
86    #[inline(always)]
87    fn default() -> Flags {
88        Flags::DEFAULTS
89    }
90}
91
92/// Information about an authorization right or the environment.
93#[repr(C)]
94pub struct AuthorizationItem(sys::AuthorizationItem);
95
96impl AuthorizationItem {
97    /// The required name of the authorization right or environment data.
98    ///
99    /// If `name` isn't convertible to a `CString` it will return
100    /// Err(errSecConversionError).
101    #[must_use]
102    pub fn name(&self) -> &str {
103        unsafe {
104            CStr::from_ptr(self.0.name)
105                .to_str()
106                .expect("AuthorizationItem::name failed to convert &str to CStr")
107        }
108    }
109
110    /// The information pertaining to the name field. Do not rely on NULL
111    /// termination of string data.
112    #[inline]
113    #[must_use]
114    pub fn value(&self) -> Option<&[u8]> {
115        if self.0.value.is_null() {
116            return None;
117        }
118
119        let value =
120            unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) };
121
122        Some(value)
123    }
124}
125
126/// A set of authorization items returned and owned by the Security Server.
127#[derive(Debug)]
128#[repr(C)]
129pub struct AuthorizationItemSet<'a> {
130    inner: *const sys::AuthorizationItemSet,
131    phantom: PhantomData<&'a sys::AuthorizationItemSet>,
132}
133
134impl<'a> Drop for AuthorizationItemSet<'a> {
135    #[inline]
136    fn drop(&mut self) {
137        unsafe {
138            sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet);
139        }
140    }
141}
142
143/// Used by `AuthorizationItemSetBuilder` to store data pointed to by
144/// `sys::AuthorizationItemSet`.
145#[derive(Debug)]
146pub struct AuthorizationItemSetStorage {
147    /// The layout of this is a little awkward because of the requirements of
148    /// Apple's APIs. `items` contains pointers to data owned by `names` and
149    /// `values`, so we must not modify them once `items` has been set up.
150    names: Vec<CString>,
151    values: Vec<Option<Vec<u8>>>,
152    items: Vec<sys::AuthorizationItem>,
153
154    /// Must not be given to APIs which would attempt to modify it.
155    ///
156    /// See `AuthorizationItemSet` for sets owned by the Security Server which
157    /// are writable.
158    pub set: sys::AuthorizationItemSet,
159}
160
161impl Default for AuthorizationItemSetStorage {
162    #[inline]
163    fn default() -> Self {
164        AuthorizationItemSetStorage {
165            names: Vec::new(),
166            values: Vec::new(),
167            items: Vec::new(),
168            set: sys::AuthorizationItemSet {
169                count: 0,
170                items: std::ptr::null_mut(),
171            },
172        }
173    }
174}
175
176/// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use
177/// rust types. All names and values passed in will be copied.
178#[derive(Debug, Default)]
179pub struct AuthorizationItemSetBuilder {
180    storage: AuthorizationItemSetStorage,
181}
182
183// Stores AuthorizationItems contiguously, and their items separately
184impl AuthorizationItemSetBuilder {
185    /// Creates a new `AuthorizationItemSetStore`, which simplifies creating
186    /// owned vectors of `AuthorizationItem`s.
187    #[inline(always)]
188    #[must_use]
189    pub fn new() -> AuthorizationItemSetBuilder {
190        Default::default()
191    }
192
193    /// Adds an `AuthorizationItem` with the name set to a right and an empty
194    /// value.
195    ///
196    /// If `name` isn't convertible to a `CString` it will return
197    /// Err(errSecConversionError).
198    pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> {
199        self.storage.names.push(cstring_or_err!(name)?);
200        self.storage.values.push(None);
201        Ok(self)
202    }
203
204    /// Adds an `AuthorizationItem` with arbitrary data.
205    ///
206    /// If `name` isn't convertible to a `CString` it will return
207    /// Err(errSecConversionError).
208    pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self>
209    where
210        N: Into<Vec<u8>>,
211        V: Into<Vec<u8>>,
212    {
213        self.storage.names.push(cstring_or_err!(name)?);
214        self.storage.values.push(Some(value.into()));
215        Ok(self)
216    }
217
218    /// Adds an `AuthorizationItem` with NULL terminated string data.
219    ///
220    /// If `name` or `value` isn't convertible to a `CString` it will return
221    /// Err(errSecConversionError).
222    pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self>
223    where
224        N: Into<Vec<u8>>,
225        V: Into<Vec<u8>>,
226    {
227        self.storage.names.push(cstring_or_err!(name)?);
228        self.storage
229            .values
230            .push(Some(cstring_or_err!(value)?.to_bytes().to_vec()));
231        Ok(self)
232    }
233
234    /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the
235    /// data it points to.
236    #[must_use]
237    pub fn build(mut self) -> AuthorizationItemSetStorage {
238        self.storage.items = self
239            .storage
240            .names
241            .iter()
242            .zip(self.storage.values.iter())
243            .map(|(n, v)| sys::AuthorizationItem {
244                name: n.as_ptr(),
245                value: v
246                    .as_ref()
247                    .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void),
248                valueLength: v.as_ref().map_or(0, |v| v.len()),
249                flags: 0,
250            })
251            .collect();
252
253        self.storage.set = sys::AuthorizationItemSet {
254            count: self.storage.items.len() as u32,
255            items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem,
256        };
257
258        self.storage
259    }
260}
261
262/// Used by `Authorization::set_item` to define the rules of he right.
263#[derive(Copy, Clone)]
264pub enum RightDefinition<'a> {
265    /// The dictionary will contain the keys and values that define the rules.
266    FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>),
267
268    /// The specified right's rules will be duplicated.
269    FromExistingRight(&'a str),
270}
271
272/// A wrapper around `AuthorizationCreate` and functions which operate on an
273/// `AuthorizationRef`.
274#[derive(Debug)]
275pub struct Authorization {
276    handle: sys::AuthorizationRef,
277    free_flags: Flags,
278}
279
280impl TryFrom<AuthorizationExternalForm> for Authorization {
281    type Error = Error;
282
283    /// Internalizes the external representation of an authorization reference.
284    #[cold]
285    fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> {
286        let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
287
288        let status = unsafe {
289            sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr())
290        };
291
292        if status != sys::errAuthorizationSuccess {
293            return Err(Error::from_code(status));
294        }
295
296        let auth = Authorization {
297            handle: unsafe { handle.assume_init() },
298            free_flags: Default::default(),
299        };
300
301        Ok(auth)
302    }
303}
304
305impl Authorization {
306    /// Creates an authorization object which has no environment or associated
307    /// rights.
308    #[allow(clippy::should_implement_trait)]
309    #[inline]
310    pub fn default() -> Result<Self> {
311        Self::new(None, None, Default::default())
312    }
313
314    /// Creates an authorization reference and provides an option to authorize
315    /// or preauthorize rights.
316    ///
317    /// `rights` should be the names of the rights you want to create.
318    ///
319    /// `environment` is used when authorizing or pre-authorizing rights. Not
320    /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass
321    /// icon or prompt data to be used in the authentication dialog box. In
322    /// macOS 10.4 and later, you can also pass a user name and password in
323    /// order to authorize a user without user interaction.
324    pub fn new(
325        // FIXME: this should have been by reference
326        rights: Option<AuthorizationItemSetStorage>,
327        environment: Option<AuthorizationItemSetStorage>,
328        flags: Flags,
329    ) -> Result<Self> {
330        let rights_ptr = rights
331            .as_ref()
332            .map_or(std::ptr::null(), |r| addr_of!(r.set) as *const _);
333
334        let env_ptr = environment
335            .as_ref()
336            .map_or(std::ptr::null(), |e| addr_of!(e.set) as *const _);
337
338        let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
339
340        let status = unsafe {
341            sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
342        };
343
344        if status != sys::errAuthorizationSuccess {
345            return Err(Error::from_code(status));
346        }
347
348        Ok(Authorization {
349            handle: unsafe { handle.assume_init() },
350            free_flags: Default::default(),
351        })
352    }
353
354    /// Internalizes the external representation of an authorization reference.
355    #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
356    pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
357        external_form.try_into()
358    }
359
360    /// By default the rights acquired will be retained by the Security Server.
361    /// Use this to ensure they are destroyed and to prevent shared rights'
362    /// continued used by other processes.
363    #[inline(always)]
364    pub fn destroy_rights(mut self) {
365        self.free_flags = Flags::DESTROY_RIGHTS;
366    }
367
368    /// Retrieve's the right's definition as a dictionary. Use `right_exists`
369    /// if you want to avoid retrieving the dictionary.
370    ///
371    /// `name` can be a wildcard right name.
372    ///
373    /// If `name` isn't convertible to a `CString` it will return
374    /// Err(errSecConversionError).
375    pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
376        let name = cstring_or_err!(name)?;
377        let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
378
379        let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
380
381        if status != sys::errAuthorizationSuccess {
382            return Err(Error::from_code(status));
383        }
384
385        let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
386
387        Ok(dict)
388    }
389
390    /// Checks if a right exists within the policy database. This is the same as
391    /// `get_right`, but avoids a dictionary allocation.
392    ///
393    /// If `name` isn't convertible to a `CString` it will return
394    /// Err(errSecConversionError).
395    pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
396        let name = cstring_or_err!(name)?;
397
398        let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
399
400        Ok(status == sys::errAuthorizationSuccess)
401    }
402
403    /// Removes a right from the policy database.
404    ///
405    /// `name` cannot be a wildcard right name.
406    ///
407    /// If `name` isn't convertible to a `CString` it will return
408    /// Err(errSecConversionError).
409    pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
410        let name = cstring_or_err!(name)?;
411
412        let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
413
414        if status != sys::errAuthorizationSuccess {
415            return Err(Error::from_code(status));
416        }
417
418        Ok(())
419    }
420
421    /// Creates or updates a right entry in the policy database. Your process
422    /// must have a code signature in order to be able to add rights to the
423    /// authorization database.
424    ///
425    /// `name` cannot be a wildcard right.
426    ///
427    /// `definition` can be either a `CFDictionaryRef` containing keys defining
428    /// the rules or a `CFStringRef` representing the name of another right
429    /// whose rules you wish to duplicate.
430    ///
431    /// `description` is a key which can be used to look up localized
432    /// descriptions.
433    ///
434    /// `bundle` will be used to get localizations from if not the main bundle.
435    ///
436    /// `localeTableName` will be used to get localizations if provided.
437    ///
438    /// If `name` isn't convertible to a `CString` it will return
439    /// Err(errSecConversionError).
440    pub fn set_right<T: Into<Vec<u8>>>(
441        &self,
442        name: T,
443        definition: RightDefinition<'_>,
444        description: Option<&str>,
445        bundle: Option<CFBundleRef>,
446        locale: Option<&str>,
447    ) -> Result<()> {
448        let name = cstring_or_err!(name)?;
449
450        let definition_cfstring: CFString;
451        let definition_ref = match definition {
452            RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
453            RightDefinition::FromExistingRight(def) => {
454                definition_cfstring = CFString::new(def);
455                definition_cfstring.as_CFTypeRef()
456            }
457        };
458
459        let status = unsafe {
460            sys::AuthorizationRightSet(
461                self.handle,
462                name.as_ptr(),
463                definition_ref,
464                optional_str_to_cfref!(description),
465                bundle.unwrap_or(std::ptr::null_mut()),
466                optional_str_to_cfref!(locale),
467            )
468        };
469
470        if status != sys::errAuthorizationSuccess {
471            return Err(Error::from_code(status));
472        }
473
474        Ok(())
475    }
476
477    /// An authorization plugin can store the results of an authentication
478    /// operation by calling the `SetContextValue` function. You can then
479    /// retrieve this supporting data, such as the user name.
480    ///
481    /// `tag` should specify the type of data the Security Server should return.
482    /// If `None`, all available information is retrieved.
483    ///
484    /// If `tag` isn't convertible to a `CString` it will return
485    /// Err(errSecConversionError).
486    pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
487        let tag_with_nul: CString;
488
489        let tag_ptr = match tag {
490            Some(tag) => {
491                tag_with_nul = cstring_or_err!(tag)?;
492                tag_with_nul.as_ptr()
493            }
494            None => std::ptr::null(),
495        };
496
497        let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
498
499        let status =
500            unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
501
502        if status != sys::errAuthorizationSuccess {
503            return Err(Error::from(status));
504        }
505
506        let set = AuthorizationItemSet {
507            inner: unsafe { inner.assume_init() },
508            phantom: PhantomData,
509        };
510
511        Ok(set)
512    }
513
514    /// Creates an external representation of an authorization reference so that
515    /// you can transmit it between processes.
516    pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
517        let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
518
519        let status =
520            unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
521
522        if status != sys::errAuthorizationSuccess {
523            return Err(Error::from(status));
524        }
525
526        Ok(unsafe { external_form.assume_init() })
527    }
528
529    /// Runs an executable tool with root privileges.
530    /// Discards executable's output
531    #[cfg(target_os = "macos")]
532    #[inline(always)]
533    pub fn execute_with_privileges<P, S, I>(
534        &self,
535        command: P,
536        arguments: I,
537        flags: Flags,
538    ) -> Result<()>
539    where
540        P: AsRef<std::path::Path>,
541        I: IntoIterator<Item = S>,
542        S: AsRef<std::ffi::OsStr>,
543    {
544        use std::os::unix::ffi::OsStrExt;
545
546        let arguments = arguments
547            .into_iter()
548            .flat_map(|a| CString::new(a.as_ref().as_bytes()))
549            .collect::<Vec<_>>();
550        self.execute_with_privileges_internal(
551            command.as_ref().as_os_str().as_bytes(),
552            &arguments,
553            flags,
554            false,
555        )?;
556        Ok(())
557    }
558
559    /// Runs an executable tool with root privileges,
560    /// and returns a `File` handle to its communication pipe
561    #[cfg(target_os = "macos")]
562    #[inline(always)]
563    pub fn execute_with_privileges_piped<P, S, I>(
564        &self,
565        command: P,
566        arguments: I,
567        flags: Flags,
568    ) -> Result<File>
569    where
570        P: AsRef<std::path::Path>,
571        I: IntoIterator<Item = S>,
572        S: AsRef<std::ffi::OsStr>,
573    {
574        use std::os::unix::ffi::OsStrExt;
575
576        let arguments = arguments
577            .into_iter()
578            .flat_map(|a| CString::new(a.as_ref().as_bytes()))
579            .collect::<Vec<_>>();
580        Ok(self
581            .execute_with_privileges_internal(
582                command.as_ref().as_os_str().as_bytes(),
583                &arguments,
584                flags,
585                true,
586            )?
587            .unwrap())
588    }
589
590    /// Submits the executable for the given label as a `launchd` job.
591    #[cfg(all(target_os = "macos", feature = "job-bless"))]
592    pub fn job_bless(&self, label: &str) -> Result<(), CFError> {
593        #[link(name = "ServiceManagement", kind = "framework")]
594        extern "C" {
595            static kSMDomainSystemLaunchd: CFStringRef;
596
597            fn SMJobBless(
598                domain: CFStringRef,
599                executableLabel: CFStringRef,
600                auth: sys::AuthorizationRef,
601                error: *mut CFErrorRef,
602            ) -> Boolean;
603        }
604
605        unsafe {
606            let mut error = std::ptr::null_mut();
607            SMJobBless(
608                kSMDomainSystemLaunchd,
609                CFString::new(label).as_concrete_TypeRef(),
610                self.handle,
611                &mut error,
612            );
613            if !error.is_null() {
614                return Err(CFError::wrap_under_create_rule(error));
615            }
616
617            Ok(())
618        }
619    }
620
621    // Runs an executable tool with root privileges.
622    #[cfg(target_os = "macos")]
623    fn execute_with_privileges_internal(
624        &self,
625        command: &[u8],
626        arguments: &[CString],
627        flags: Flags,
628        make_pipe: bool,
629    ) -> Result<Option<File>> {
630        use std::os::unix::io::{FromRawFd, RawFd};
631
632        let c_cmd = cstring_or_err!(command)?;
633
634        let mut c_args = arguments
635            .iter()
636            .map(|a| a.as_ptr() as _)
637            .collect::<Vec<_>>();
638        c_args.push(std::ptr::null_mut());
639
640        let mut pipe: *mut libc::FILE = std::ptr::null_mut();
641
642        let status = unsafe {
643            sys::AuthorizationExecuteWithPrivileges(
644                self.handle,
645                c_cmd.as_ptr(),
646                flags.bits(),
647                c_args.as_ptr(),
648                if make_pipe {
649                    &mut pipe
650                } else {
651                    std::ptr::null_mut()
652                },
653            )
654        };
655
656        crate::cvt(status)?;
657        Ok(if make_pipe {
658            if pipe.is_null() {
659                return Err(Error::from_code(32)); // EPIPE?
660            }
661            Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) })
662        } else {
663            None
664        })
665    }
666}
667
668impl Drop for Authorization {
669    #[inline]
670    fn drop(&mut self) {
671        unsafe {
672            sys::AuthorizationFree(self.handle, self.free_flags.bits());
673        }
674    }
675}
676
677#[cfg(test)]
678mod tests {
679    use core_foundation::string::CFString;
680
681    use super::*;
682
683    #[test]
684    fn test_create_default_authorization() {
685        Authorization::default().unwrap();
686    }
687
688    #[test]
689    fn test_create_allowed_authorization() -> Result<()> {
690        let rights = AuthorizationItemSetBuilder::new()
691            .add_right("system.hdd.smart")?
692            .add_right("system.login.done")?
693            .build();
694
695        Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
696
697        Ok(())
698    }
699
700    #[test]
701    fn test_create_then_destroy_allowed_authorization() -> Result<()> {
702        let rights = AuthorizationItemSetBuilder::new()
703            .add_right("system.hdd.smart")?
704            .add_right("system.login.done")?
705            .build();
706
707        let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
708        auth.destroy_rights();
709
710        Ok(())
711    }
712
713    #[test]
714    fn test_create_authorization_requiring_interaction() -> Result<()> {
715        let rights = AuthorizationItemSetBuilder::new()
716            .add_right("system.privilege.admin")?
717            .build();
718
719        let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
720
721        assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
722
723        Ok(())
724    }
725
726    fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
727        #![allow(clippy::option_env_unwrap)]
728
729        let set = AuthorizationItemSetBuilder::new()
730            .add_string(
731                "username",
732                option_env!("USER").expect("You must set the USER environment variable"),
733            )?
734            .add_string(
735                "password",
736                option_env!("PASSWORD").expect("You must set the PASSWORD environment variable"),
737            )?
738            .build();
739
740        Ok(set)
741    }
742
743    #[test]
744    fn test_create_authorization_with_bad_credentials() -> Result<()> {
745        let rights = AuthorizationItemSetBuilder::new()
746            .add_right("system.privilege.admin")?
747            .build();
748
749        let env = AuthorizationItemSetBuilder::new()
750            .add_string("username", "Tim Apple")?
751            .add_string("password", "butterfly")?
752            .build();
753
754        let error =
755            Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
756
757        assert_eq!(error.code(), sys::errAuthorizationDenied);
758
759        Ok(())
760    }
761
762    #[test]
763    fn test_create_authorization_with_credentials() -> Result<()> {
764        if option_env!("PASSWORD").is_none() {
765            return Ok(());
766        }
767
768        let rights = AuthorizationItemSetBuilder::new()
769            .add_right("system.privilege.admin")?
770            .build();
771
772        let env = create_credentials_env()?;
773
774        Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
775
776        Ok(())
777    }
778
779    #[test]
780    fn test_query_authorization_database() -> Result<()> {
781        assert!(Authorization::right_exists("system.hdd.smart")?);
782        assert!(!Authorization::right_exists("EMPTY")?);
783
784        let dict = Authorization::get_right("system.hdd.smart").unwrap();
785
786        let key = CFString::from_static_string("class");
787        assert!(dict.contains_key(&key));
788
789        let invalid_key = CFString::from_static_string("EMPTY");
790        assert!(!dict.contains_key(&invalid_key));
791
792        Ok(())
793    }
794
795    /// This test will only pass if its process has a valid code signature.
796    #[test]
797    fn test_modify_authorization_database() -> Result<()> {
798        if option_env!("PASSWORD").is_none() {
799            return Ok(());
800        }
801
802        let rights = AuthorizationItemSetBuilder::new()
803            .add_right("config.modify.")?
804            .build();
805
806        let env = create_credentials_env()?;
807
808        let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
809
810        assert!(!Authorization::right_exists("TEST_RIGHT")?);
811
812        auth.set_right(
813            "TEST_RIGHT",
814            RightDefinition::FromExistingRight("system.hdd.smart"),
815            None,
816            None,
817            None,
818        )
819        .unwrap();
820
821        assert!(Authorization::right_exists("TEST_RIGHT")?);
822
823        auth.remove_right("TEST_RIGHT").unwrap();
824
825        assert!(!Authorization::right_exists("TEST_RIGHT")?);
826
827        Ok(())
828    }
829
830    /// This test will succeed if authorization popup is approved.
831    #[test]
832    fn test_execute_with_privileges() -> Result<()> {
833        if option_env!("PASSWORD").is_none() {
834            return Ok(());
835        }
836
837        let rights = AuthorizationItemSetBuilder::new()
838            .add_right("system.privilege.admin")?
839            .build();
840
841        let auth = Authorization::new(
842            Some(rights),
843            None,
844            Flags::DEFAULTS
845                | Flags::INTERACTION_ALLOWED
846                | Flags::PREAUTHORIZE
847                | Flags::EXTEND_RIGHTS,
848        )?;
849
850        let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?;
851
852        use std::io::{
853            BufRead, {self},
854        };
855        for line in io::BufReader::new(file).lines() {
856            let _ = line.unwrap();
857        }
858
859        Ok(())
860    }
861}