| //! Authorization Services support. |
| |
| /// # Potential improvements |
| /// |
| /// * When generic specialization stabilizes prevent copying from CString |
| /// arguments. |
| /// * AuthorizationCopyRightsAsync |
| /// * Provide constants for well known item names |
| use crate::base::{Error, Result}; |
| use core_foundation::base::{CFTypeRef, TCFType}; |
| use core_foundation::bundle::CFBundleRef; |
| use core_foundation::dictionary::{CFDictionary, CFDictionaryRef}; |
| use core_foundation::string::{CFString, CFStringRef}; |
| use security_framework_sys::authorization as sys; |
| use security_framework_sys::base::errSecConversionError; |
| use std::mem::MaybeUninit; |
| use std::os::raw::c_void; |
| use std::{ |
| convert::TryFrom, |
| ffi::{CStr, CString}, |
| fs::File, |
| }; |
| use std::{convert::TryInto, marker::PhantomData}; |
| use sys::AuthorizationExternalForm; |
| |
| macro_rules! optional_str_to_cfref { |
| ($string:ident) => {{ |
| $string.map(CFString::new).map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef()) |
| }}; |
| } |
| |
| macro_rules! cstring_or_err { |
| ($x:expr) => {{ |
| CString::new($x).map_err(|_| Error::from_code(errSecConversionError)) |
| }}; |
| } |
| |
| bitflags::bitflags! { |
| /// The flags used to specify authorization options. |
| pub struct Flags: sys::AuthorizationFlags { |
| /// An empty flag set that you use as a placeholder when you don't want |
| /// any of the other flags. |
| const DEFAULTS = sys::kAuthorizationFlagDefaults; |
| |
| /// A flag that permits user interaction as needed. |
| const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed; |
| |
| /// A flag that permits the Security Server to attempt to grant the |
| /// rights requested. |
| const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights; |
| |
| /// A flag that permits the Security Server to grant rights on an |
| /// individual basis. |
| const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights; |
| |
| /// A flag that instructs the Security Server to revoke authorization. |
| const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights; |
| |
| /// A flag that instructs the Security Server to preauthorize the rights |
| /// requested. |
| const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize; |
| } |
| } |
| |
| impl Default for Flags { |
| #[inline(always)] |
| fn default() -> Flags { |
| Flags::DEFAULTS |
| } |
| } |
| |
| /// Information about an authorization right or the environment. |
| #[repr(C)] |
| pub struct AuthorizationItem(sys::AuthorizationItem); |
| |
| impl AuthorizationItem { |
| /// The required name of the authorization right or environment data. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn name(&self) -> &str { |
| unsafe { |
| CStr::from_ptr(self.0.name) |
| .to_str() |
| .expect("AuthorizationItem::name failed to convert &str to CStr") |
| } |
| } |
| |
| /// The information pertaining to the name field. Do not rely on NULL |
| /// termination of string data. |
| pub fn value(&self) -> Option<&[u8]> { |
| if self.0.value.is_null() { |
| return None; |
| } |
| |
| let value = |
| unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) }; |
| |
| Some(value) |
| } |
| } |
| |
| /// A set of authorization items returned and owned by the Security Server. |
| #[derive(Debug)] |
| #[repr(C)] |
| pub struct AuthorizationItemSet<'a> { |
| inner: *const sys::AuthorizationItemSet, |
| phantom: PhantomData<&'a sys::AuthorizationItemSet>, |
| } |
| |
| impl<'a> Drop for AuthorizationItemSet<'a> { |
| #[inline] |
| fn drop(&mut self) { |
| unsafe { |
| sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet); |
| } |
| } |
| } |
| |
| /// Used by `AuthorizationItemSetBuilder` to store data pointed to by |
| /// `sys::AuthorizationItemSet`. |
| #[derive(Debug)] |
| pub struct AuthorizationItemSetStorage { |
| /// The layout of this is a little awkward because of the requirements of |
| /// Apple's APIs. `items` contains pointers to data owned by `names` and |
| /// `values`, so we must not modify them once `items` has been set up. |
| names: Vec<CString>, |
| values: Vec<Option<Vec<u8>>>, |
| items: Vec<sys::AuthorizationItem>, |
| |
| /// Must not be given to APIs which would attempt to modify it. |
| /// |
| /// See `AuthorizationItemSet` for sets owned by the Security Server which |
| /// are writable. |
| pub set: sys::AuthorizationItemSet, |
| } |
| |
| impl Default for AuthorizationItemSetStorage { |
| #[inline] |
| fn default() -> Self { |
| AuthorizationItemSetStorage { |
| names: Vec::new(), |
| values: Vec::new(), |
| items: Vec::new(), |
| set: sys::AuthorizationItemSet { count: 0, items: std::ptr::null_mut() }, |
| } |
| } |
| } |
| |
| /// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use |
| /// rust types. All names and values passed in will be copied. |
| #[derive(Debug, Default)] |
| pub struct AuthorizationItemSetBuilder { |
| storage: AuthorizationItemSetStorage, |
| } |
| |
| // Stores AuthorizationItems contiguously, and their items separately |
| impl AuthorizationItemSetBuilder { |
| /// Creates a new `AuthorizationItemSetStore`, which simplifies creating |
| /// owned vectors of `AuthorizationItem`s. |
| #[inline(always)] |
| pub fn new() -> AuthorizationItemSetBuilder { |
| Default::default() |
| } |
| |
| /// Adds an AuthorizationItem with the name set to a right and an empty |
| /// value. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> { |
| self.storage.names.push(cstring_or_err!(name)?); |
| self.storage.values.push(None); |
| Ok(self) |
| } |
| |
| /// Adds an AuthorizationItem with arbitrary data. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self> |
| where |
| N: Into<Vec<u8>>, |
| V: Into<Vec<u8>>, |
| { |
| self.storage.names.push(cstring_or_err!(name)?); |
| self.storage.values.push(Some(value.into())); |
| Ok(self) |
| } |
| |
| /// Adds an AuthorizationItem with NULL terminated string data. |
| /// |
| /// If `name` or `value` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self> |
| where |
| N: Into<Vec<u8>>, |
| V: Into<Vec<u8>>, |
| { |
| self.storage.names.push(cstring_or_err!(name)?); |
| self.storage.values.push(Some(cstring_or_err!(value)?.to_bytes().to_vec())); |
| Ok(self) |
| } |
| |
| /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the |
| /// data it points to. |
| pub fn build(mut self) -> AuthorizationItemSetStorage { |
| self.storage.items = self |
| .storage |
| .names |
| .iter() |
| .zip(self.storage.values.iter()) |
| .map(|(n, v)| sys::AuthorizationItem { |
| name: n.as_ptr(), |
| value: v.as_ref().map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void), |
| valueLength: v.as_ref().map_or(0, |v| v.len()), |
| flags: 0, |
| }) |
| .collect(); |
| |
| self.storage.set = sys::AuthorizationItemSet { |
| count: self.storage.items.len() as u32, |
| items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem, |
| }; |
| |
| self.storage |
| } |
| } |
| |
| /// Used by `Authorization::set_item` to define the rules of he right. |
| pub enum RightDefinition<'a> { |
| /// The dictionary will contain the keys and values that define the rules. |
| FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>), |
| |
| /// The specified right's rules will be duplicated. |
| FromExistingRight(&'a str), |
| } |
| |
| /// A wrapper around AuthorizationCreate and functions which operate on an |
| /// AuthorizationRef. |
| #[derive(Debug)] |
| pub struct Authorization { |
| handle: sys::AuthorizationRef, |
| free_flags: Flags, |
| } |
| |
| impl TryFrom<AuthorizationExternalForm> for Authorization { |
| type Error = Error; |
| |
| /// Internalizes the external representation of an authorization reference. |
| #[cold] |
| fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> { |
| let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); |
| |
| let status = unsafe { |
| sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr()) |
| }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from_code(status)); |
| } |
| |
| let auth = Authorization { |
| handle: unsafe { handle.assume_init() }, |
| free_flags: Default::default(), |
| }; |
| |
| Ok(auth) |
| } |
| } |
| |
| impl<'a> Authorization { |
| /// Creates an authorization object which has no environment or associated |
| /// rights. |
| #[inline] |
| pub fn default() -> Result<Self> { |
| Self::new(None, None, Default::default()) |
| } |
| |
| /// Creates an authorization reference and provides an option to authorize |
| /// or preauthorize rights. |
| /// |
| /// `rights` should be the names of the rights you want to create. |
| /// |
| /// `environment` is used when authorizing or preauthorizing rights. Not |
| /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass |
| /// icon or prompt data to be used in the authentication dialog box. In |
| /// macOS 10.4 and later, you can also pass a user name and password in |
| /// order to authorize a user without user interaction. |
| pub fn new( |
| rights: Option<AuthorizationItemSetStorage>, |
| environment: Option<AuthorizationItemSetStorage>, |
| flags: Flags, |
| ) -> Result<Self> { |
| let rights_ptr = rights |
| .as_ref() |
| .map_or(std::ptr::null(), |r| &r.set as *const sys::AuthorizationItemSet); |
| |
| let env_ptr = environment |
| .as_ref() |
| .map_or(std::ptr::null(), |e| &e.set as *const sys::AuthorizationItemSet); |
| |
| let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); |
| |
| let status = unsafe { |
| sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr()) |
| }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from_code(status)); |
| } |
| |
| Ok(Authorization { |
| handle: unsafe { handle.assume_init() }, |
| free_flags: Default::default(), |
| }) |
| } |
| |
| /// Internalizes the external representation of an authorization reference. |
| #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")] |
| pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> { |
| external_form.try_into() |
| } |
| |
| /// By default the rights acquired will be retained by the Security Server. |
| /// Use this to ensure they are destroyed and to prevent shared rights' |
| /// continued used by other processes. |
| #[inline(always)] |
| pub fn destroy_rights(mut self) { |
| self.free_flags = Flags::DESTROY_RIGHTS; |
| } |
| |
| /// Retrieve's the right's definition as a dictionary. Use `right_exists` |
| /// if you want to avoid retrieving the dictionary. |
| /// |
| /// `name` can be a wildcard right name. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> { |
| let name = cstring_or_err!(name)?; |
| let mut dict = MaybeUninit::<CFDictionaryRef>::uninit(); |
| |
| let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from_code(status)); |
| } |
| |
| let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) }; |
| |
| Ok(dict) |
| } |
| |
| /// Checks if a right exists within the policy database. This is the same as |
| /// `get_right`, but avoids a dictionary allocation. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> { |
| let name = cstring_or_err!(name)?; |
| |
| let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) }; |
| |
| Ok(status == sys::errAuthorizationSuccess) |
| } |
| |
| /// Removes a right from the policy database. |
| /// |
| /// `name` cannot be a wildcard right name. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> { |
| let name = cstring_or_err!(name)?; |
| |
| let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from_code(status)); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Creates or updates a right entry in the policy database. Your process |
| /// must have a code signature in order to be able to add rights to the |
| /// authorization database. |
| /// |
| /// `name` cannot be a wildcard right. |
| /// |
| /// `definition` can be either a `CFDictionaryRef` containing keys defining |
| /// the rules or a `CFStringRef` representing the name of another right |
| /// whose rules you wish to duplicaate. |
| /// |
| /// `description` is a key which can be used to look up localized |
| /// descriptions. |
| /// |
| /// `bundle` will be used to get localizations from if not the main bundle. |
| /// |
| /// `localeTableName` will be used to get localizations if provided. |
| /// |
| /// If `name` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn set_right<T: Into<Vec<u8>>>( |
| &self, |
| name: T, |
| definition: RightDefinition<'_>, |
| description: Option<&str>, |
| bundle: Option<CFBundleRef>, |
| locale: Option<&str>, |
| ) -> Result<()> { |
| let name = cstring_or_err!(name)?; |
| |
| let definition_cfstring: CFString; |
| let definition_ref = match definition { |
| RightDefinition::FromDictionary(def) => def.as_CFTypeRef(), |
| RightDefinition::FromExistingRight(def) => { |
| definition_cfstring = CFString::new(def); |
| definition_cfstring.as_CFTypeRef() |
| } |
| }; |
| |
| let status = unsafe { |
| sys::AuthorizationRightSet( |
| self.handle, |
| name.as_ptr(), |
| definition_ref, |
| optional_str_to_cfref!(description), |
| bundle.unwrap_or(std::ptr::null_mut()), |
| optional_str_to_cfref!(locale), |
| ) |
| }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from_code(status)); |
| } |
| |
| Ok(()) |
| } |
| |
| /// An authorization plugin can store the results of an authentication |
| /// operation by calling the `SetContextValue` function. You can then |
| /// retrieve this supporting data, such as the user name. |
| /// |
| /// `tag` should specify the type of data the Security Server should return. |
| /// If `None`, all available information is retreieved. |
| /// |
| /// If `tag` isn't convertable to a `CString` it will return |
| /// Err(errSecConversionError). |
| pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> { |
| let tag_with_nul: CString; |
| |
| let tag_ptr = match tag { |
| Some(tag) => { |
| tag_with_nul = cstring_or_err!(tag)?; |
| tag_with_nul.as_ptr() |
| } |
| None => std::ptr::null(), |
| }; |
| |
| let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit(); |
| |
| let status = |
| unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from(status)); |
| } |
| |
| let set = |
| AuthorizationItemSet { inner: unsafe { inner.assume_init() }, phantom: PhantomData }; |
| |
| Ok(set) |
| } |
| |
| /// Creates an external representation of an authorization reference so that |
| /// you can transmit it between processes. |
| pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> { |
| let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit(); |
| |
| let status = |
| unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) }; |
| |
| if status != sys::errAuthorizationSuccess { |
| return Err(Error::from(status)); |
| } |
| |
| Ok(unsafe { external_form.assume_init() }) |
| } |
| |
| /// Runs an executable tool with root privileges. |
| /// Discards executable's output |
| #[cfg(target_os = "macos")] |
| #[inline(always)] |
| pub fn execute_with_privileges<P, S, I>( |
| &self, |
| command: P, |
| arguments: I, |
| flags: Flags, |
| ) -> Result<()> |
| where |
| P: AsRef<std::path::Path>, |
| I: IntoIterator<Item = S>, |
| S: AsRef<std::ffi::OsStr>, |
| { |
| use std::os::unix::ffi::OsStrExt; |
| |
| let arguments = arguments |
| .into_iter() |
| .map(|a| CString::new(a.as_ref().as_bytes())) |
| .flatten() |
| .collect::<Vec<_>>(); |
| self.execute_with_privileges_internal( |
| command.as_ref().as_os_str().as_bytes(), |
| &arguments, |
| flags, |
| false, |
| )?; |
| Ok(()) |
| } |
| |
| /// Runs an executable tool with root privileges, |
| /// and returns a `File` handle to its communication pipe |
| #[cfg(target_os = "macos")] |
| #[inline(always)] |
| pub fn execute_with_privileges_piped<P, S, I>( |
| &self, |
| command: P, |
| arguments: I, |
| flags: Flags, |
| ) -> Result<File> |
| where |
| P: AsRef<std::path::Path>, |
| I: IntoIterator<Item = S>, |
| S: AsRef<std::ffi::OsStr>, |
| { |
| use std::os::unix::ffi::OsStrExt; |
| |
| let arguments = arguments |
| .into_iter() |
| .map(|a| CString::new(a.as_ref().as_bytes())) |
| .flatten() |
| .collect::<Vec<_>>(); |
| Ok(self |
| .execute_with_privileges_internal( |
| command.as_ref().as_os_str().as_bytes(), |
| &arguments, |
| flags, |
| true, |
| )? |
| .unwrap()) |
| } |
| |
| // Runs an executable tool with root privileges. |
| #[cfg(target_os = "macos")] |
| fn execute_with_privileges_internal( |
| &self, |
| command: &[u8], |
| arguments: &[CString], |
| flags: Flags, |
| make_pipe: bool, |
| ) -> Result<Option<File>> { |
| use std::os::unix::io::{FromRawFd, RawFd}; |
| |
| let c_cmd = cstring_or_err!(command)?; |
| |
| let mut c_args = arguments.iter().map(|a| a.as_ptr() as _).collect::<Vec<_>>(); |
| c_args.push(std::ptr::null_mut()); |
| |
| let mut pipe: *mut libc::FILE = std::ptr::null_mut(); |
| |
| let status = unsafe { |
| sys::AuthorizationExecuteWithPrivileges( |
| self.handle, |
| c_cmd.as_ptr(), |
| flags.bits(), |
| c_args.as_ptr(), |
| if make_pipe { &mut pipe } else { std::ptr::null_mut() }, |
| ) |
| }; |
| |
| crate::cvt(status)?; |
| Ok(if make_pipe { |
| if pipe.is_null() { |
| return Err(Error::from_code(32)); // EPIPE? |
| } |
| Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) }) |
| } else { |
| None |
| }) |
| } |
| } |
| |
| impl Drop for Authorization { |
| #[inline] |
| fn drop(&mut self) { |
| unsafe { |
| sys::AuthorizationFree(self.handle, self.free_flags.bits()); |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use core_foundation::string::CFString; |
| |
| #[test] |
| fn test_create_default_authorization() { |
| Authorization::default().unwrap(); |
| } |
| |
| #[test] |
| fn test_create_allowed_authorization() -> Result<()> { |
| let rights = AuthorizationItemSetBuilder::new() |
| .add_right("system.hdd.smart")? |
| .add_right("system.login.done")? |
| .build(); |
| |
| Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_create_then_destroy_allowed_authorization() -> Result<()> { |
| let rights = AuthorizationItemSetBuilder::new() |
| .add_right("system.hdd.smart")? |
| .add_right("system.login.done")? |
| .build(); |
| |
| let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); |
| auth.destroy_rights(); |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_create_authorization_requiring_interaction() -> Result<()> { |
| let rights = |
| AuthorizationItemSetBuilder::new().add_right("system.privilege.admin")?.build(); |
| |
| let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err(); |
| |
| assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed); |
| |
| Ok(()) |
| } |
| |
| fn create_credentials_env() -> Result<AuthorizationItemSetStorage> { |
| let set = AuthorizationItemSetBuilder::new() |
| .add_string( |
| "username", |
| option_env!("USER").expect("You must set the USER environment variable"), |
| )? |
| .add_string( |
| "password", |
| option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"), |
| )? |
| .build(); |
| |
| Ok(set) |
| } |
| |
| #[test] |
| fn test_create_authorization_with_bad_credentials() -> Result<()> { |
| let rights = |
| AuthorizationItemSetBuilder::new().add_right("system.privilege.admin")?.build(); |
| |
| let env = AuthorizationItemSetBuilder::new() |
| .add_string("username", "Tim Apple")? |
| .add_string("password", "butterfly")? |
| .build(); |
| |
| let error = |
| Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err(); |
| |
| assert_eq!(error.code(), sys::errAuthorizationDenied); |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_create_authorization_with_credentials() -> Result<()> { |
| if option_env!("PASSWORD").is_none() { |
| return Ok(()); |
| } |
| |
| let rights = |
| AuthorizationItemSetBuilder::new().add_right("system.privilege.admin")?.build(); |
| |
| let env = create_credentials_env()?; |
| |
| Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_query_authorization_database() -> Result<()> { |
| assert!(Authorization::right_exists("system.hdd.smart")?); |
| assert!(!Authorization::right_exists("EMPTY")?); |
| |
| let dict = Authorization::get_right("system.hdd.smart").unwrap(); |
| |
| let key = CFString::from_static_string("class"); |
| assert!(dict.contains_key(&key)); |
| |
| let invalid_key = CFString::from_static_string("EMPTY"); |
| assert!(!dict.contains_key(&invalid_key)); |
| |
| Ok(()) |
| } |
| |
| /// This test will only pass if its process has a valid code signature. |
| #[test] |
| fn test_modify_authorization_database() -> Result<()> { |
| if option_env!("PASSWORD").is_none() { |
| return Ok(()); |
| } |
| |
| let rights = AuthorizationItemSetBuilder::new().add_right("config.modify.")?.build(); |
| |
| let env = create_credentials_env()?; |
| |
| let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); |
| |
| assert!(!Authorization::right_exists("TEST_RIGHT")?); |
| |
| auth.set_right( |
| "TEST_RIGHT", |
| RightDefinition::FromExistingRight("system.hdd.smart"), |
| None, |
| None, |
| None, |
| ) |
| .unwrap(); |
| |
| assert!(Authorization::right_exists("TEST_RIGHT")?); |
| |
| auth.remove_right("TEST_RIGHT").unwrap(); |
| |
| assert!(!Authorization::right_exists("TEST_RIGHT")?); |
| |
| Ok(()) |
| } |
| |
| /// This test will succeed if authorization popup is approved. |
| #[test] |
| fn test_execute_with_privileges() -> Result<()> { |
| if option_env!("PASSWORD").is_none() { |
| return Ok(()); |
| } |
| |
| let rights = |
| AuthorizationItemSetBuilder::new().add_right("system.privilege.admin")?.build(); |
| |
| let auth = Authorization::new( |
| Some(rights), |
| None, |
| Flags::DEFAULTS |
| | Flags::INTERACTION_ALLOWED |
| | Flags::PREAUTHORIZE |
| | Flags::EXTEND_RIGHTS, |
| )?; |
| |
| let file = auth.execute_with_privileges_piped("/bin/ls", &["/"], Flags::DEFAULTS)?; |
| |
| use std::io::{self, BufRead}; |
| for line in io::BufReader::new(file).lines() { |
| let _ = line.unwrap(); |
| } |
| |
| Ok(()) |
| } |
| } |