use security_framework::base::Error;
use security_framework::passwords::{
delete_generic_password, get_generic_password, set_generic_password,
};
use super::credential::{Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi};
use super::error::{Error as ErrorCode, Result, decode_password};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IosCredential {
pub service: String,
pub account: String,
}
impl CredentialApi for IosCredential {
fn set_password(&self, password: &str) -> Result<()> {
self.set_secret(password.as_bytes())?;
Ok(())
}
fn set_secret(&self, secret: &[u8]) -> Result<()> {
set_generic_password(&self.service, &self.account, secret).map_err(decode_error)?;
Ok(())
}
fn get_password(&self) -> Result<String> {
let password_bytes = self.get_secret()?;
decode_password(password_bytes)
}
fn get_secret(&self) -> Result<Vec<u8>> {
get_generic_password(&self.service, &self.account).map_err(decode_error)
}
fn delete_credential(&self) -> Result<()> {
delete_generic_password(&self.service, &self.account).map_err(decode_error)?;
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl IosCredential {
pub fn get_credential(&self) -> Result<Self> {
get_generic_password(&self.service, &self.account).map_err(decode_error)?;
Ok(self.clone())
}
pub fn new_with_target(target: Option<&str>, service: &str, user: &str) -> Result<Self> {
if service.is_empty() {
return Err(ErrorCode::Invalid(
"service".to_string(),
"cannot be empty".to_string(),
));
}
if user.is_empty() {
return Err(ErrorCode::Invalid(
"user".to_string(),
"cannot be empty".to_string(),
));
}
if let Some(target) = target {
if !target.eq_ignore_ascii_case("default") {
return Err(ErrorCode::Invalid(
"target".to_string(),
"only 'default' is allowed".to_string(),
));
}
}
Ok(Self {
service: service.to_string(),
account: user.to_string(),
})
}
}
pub struct IosCredentialBuilder {}
pub fn default_credential_builder() -> Box<CredentialBuilder> {
Box::new(IosCredentialBuilder {})
}
impl CredentialBuilderApi for IosCredentialBuilder {
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>> {
Ok(Box::new(IosCredential::new_with_target(
target, service, user,
)?))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
fn decode_error(err: Error) -> ErrorCode {
match err.code() {
-25291 => ErrorCode::NoStorageAccess(Box::new(err)), -25292 => ErrorCode::NoStorageAccess(Box::new(err)), -25300 => ErrorCode::NoEntry, _ => ErrorCode::PlatformFailure(Box::new(err)),
}
}
#[cfg(test)]
mod tests {
use super::{IosCredential, default_credential_builder};
use crate::credential::CredentialPersistence;
use crate::{Entry, Error, tests::generate_random_string};
#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::UntilDelete
))
}
fn entry_new(service: &str, user: &str) -> Entry {
crate::tests::entry_from_constructor(IosCredential::new_with_target, service, user)
}
#[test]
fn test_invalid_parameter() {
let credential = IosCredential::new_with_target(None, "", "user");
assert!(
matches!(credential, Err(Error::Invalid(_, _))),
"Created credential with empty service"
);
let credential = IosCredential::new_with_target(None, "service", "");
assert!(
matches!(credential, Err(Error::Invalid(_, _))),
"Created entry with empty user"
);
let credential = IosCredential::new_with_target(Some(""), "service", "user");
assert!(
matches!(credential, Err(Error::Invalid(_, _))),
"Created entry with empty target"
);
}
#[test]
fn test_missing_entry() {
crate::tests::test_missing_entry(entry_new);
}
#[test]
fn test_empty_password() {
crate::tests::test_empty_password(entry_new);
}
#[test]
fn test_round_trip_ascii_password() {
crate::tests::test_round_trip_ascii_password(entry_new);
}
#[test]
fn test_round_trip_non_ascii_password() {
crate::tests::test_round_trip_non_ascii_password(entry_new);
}
#[test]
fn test_round_trip_random_secret() {
crate::tests::test_round_trip_random_secret(entry_new);
}
#[test]
fn test_update() {
crate::tests::test_update(entry_new);
}
#[test]
fn test_get_credential() {
let name = generate_random_string();
let entry = entry_new(&name, &name);
let credential: &IosCredential = entry
.get_credential()
.downcast_ref()
.expect("Not a mac credential");
assert!(
credential.get_credential().is_err(),
"Platform credential shouldn't exist yet!"
);
entry
.set_password("test get_credential")
.expect("Can't set password for get_credential");
assert!(credential.get_credential().is_ok());
entry
.delete_credential()
.expect("Couldn't delete after get_credential");
assert!(matches!(entry.get_password(), Err(Error::NoEntry)));
}
#[test]
fn test_get_update_attributes() {
crate::tests::test_noop_get_update_attributes(entry_new);
}
}