security_framework/
authorization.rs

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