Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions security-framework/src/passwords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ pub fn generic_password(mut options: PasswordOptions) -> Result<Vec<u8>> {
/// If none exists, fails with error code `errSecItemNotFound`.
pub fn delete_generic_password(service: &str, account: &str) -> Result<()> {
let options = PasswordOptions::new_generic_password(service, account);
delete_generic_password_options(options)
}

/// Delete the generic password keychain entry for the given service and account.
/// If none exists, fails with error code `errSecItemNotFound`.
///
/// See [`PasswordOptions`] and [`new_generic_password`](PasswordOptions::new_generic_password).
///
/// ```rust
/// use security_framework::passwords::{delete_generic_password_options, PasswordOptions};
/// delete_generic_password_options(PasswordOptions::new_generic_password("service", "account"));
/// ```
pub fn delete_generic_password_options(options: PasswordOptions) -> Result<()> {
let params = options.to_dictionary();
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
}
Expand Down
41 changes: 41 additions & 0 deletions security-framework/src/passwords_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

use crate::access_control::SecAccessControl;
use core_foundation::base::{CFOptionFlags, CFType, TCFType};
#[allow(unused_imports)]
use core_foundation::boolean::CFBoolean;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::{CFString, CFStringRef};
Expand All @@ -14,6 +16,10 @@ use security_framework_sys::item::{
kSecAttrPath, kSecAttrPort, kSecAttrProtocol, kSecAttrSecurityDomain, kSecAttrServer,
kSecAttrService, kSecClass, kSecClassGenericPassword, kSecClassInternetPassword,
};
#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
use security_framework_sys::item::kSecAttrSynchronizable;
#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
use security_framework_sys::item::kSecAttrSynchronizableAny;
use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};

/// `PasswordOptions` constructor
Expand Down Expand Up @@ -130,6 +136,41 @@ impl PasswordOptions {
}
}

#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
/// Specify whether password is cloud-synchronized, not cloud-synchronized, or either (`None`).
///
/// Note: cloud-synchronized and not-cloud-synchronized passwords are kept
/// in completely different stores, so they are uniquely identified not just
/// by their `service` and `account` but also their cloud-synchronized option.
///
/// If you specify a non-`None` value for this option, any operation you
/// perform - whether set, get, or delete - will only affect the store matching
/// the value: Some(`true`) will only affect the cloud-synchronized store and
/// Some(`false`) will only affect the not-cloud-synchronized store.
///
/// If you specify `None` for this option, the effect depends on your operation:
///
/// - Performing a delete will delete from both stores.
/// - Performing a get will return values from both stores, but since get only
/// returns one value you can't be sure which store that value was in.
/// - Performing a set will update existing values in both stores. _But_, before
/// doing any updates, set will first try to create a new value in the
/// not-cloud-synchronized store (interpreting `None` as `false`). If
/// that creation attempt succeeds, no update will be done of any existing
/// value in the cloud-synchronized store. Thus, only if there is an existing
/// value in the not-cloud-synchronized store will set update the
/// cloud-synchronized store.
pub fn set_access_synchronized(&mut self, synchronized: Option<bool>) {
unsafe {
if let Some(synchronizable) = synchronized {
self.push_query(kSecAttrSynchronizable, CFBoolean::from(synchronizable));
} else {
let either = CFString::wrap_under_get_rule(kSecAttrSynchronizableAny);
self.push_query(kSecAttrSynchronizable, either);
}
}
}

/// The key must be a `kSec*` constant.
/// Value is any owned ObjC object, like `CFString`.
pub(crate) unsafe fn push_query(&mut self, static_key_constant: CFStringRef, value: impl TCFType) {
Expand Down
Loading