Implement Android Credential Manager and Autofill providers (#109)
This commit is contained in:
1 parent
316a875d63
commit
0c8366d34f
48 files changed
+4138
-78
No files matched your search
@@ -0,0 +1,210 @@
|
||||
//! Android provider bindings; all credential decisions remain in storage.
|
||||
use crate::{MobileAuthentication, MobileAuthenticationFfiError};
|
||||
use ironstorage::mobile_credentials as storage;
|
||||
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileCredentialCaller {
|
||||
pub package: String,
|
||||
pub certificates: Vec<String>,
|
||||
pub origin: Option<String>,
|
||||
}
|
||||
impl From<MobileCredentialCaller> for storage::CredentialCaller {
|
||||
fn from(value: MobileCredentialCaller) -> Self {
|
||||
Self {
|
||||
package: value.package,
|
||||
certificates: value.certificates,
|
||||
origin: value.origin,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileCredentialAttribute {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileCredentialField {
|
||||
pub index: u32,
|
||||
pub hints: Vec<String>,
|
||||
pub html_attributes: Vec<MobileCredentialAttribute>,
|
||||
pub input_type: u32,
|
||||
pub focused: bool,
|
||||
pub editable: bool,
|
||||
}
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileCredentialForm {
|
||||
pub username: u32,
|
||||
pub password: u32,
|
||||
}
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileCredentialCandidate {
|
||||
pub path: String,
|
||||
pub matched: bool,
|
||||
}
|
||||
#[derive(Clone, Debug, uniffi::Record)]
|
||||
pub struct MobileProviderRequest {
|
||||
pub token: String,
|
||||
pub title: String,
|
||||
pub revision: String,
|
||||
pub candidates: Vec<MobileCredentialCandidate>,
|
||||
}
|
||||
impl From<storage::CredentialRequest> for MobileProviderRequest {
|
||||
fn from(value: storage::CredentialRequest) -> Self {
|
||||
Self {
|
||||
token: value.token,
|
||||
title: value.identity.title,
|
||||
revision: value.revision,
|
||||
candidates: value
|
||||
.candidates
|
||||
.into_iter()
|
||||
.map(|candidate| MobileCredentialCandidate {
|
||||
path: candidate.path,
|
||||
matched: candidate.matched,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct MobileCredentialValue {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
impl std::fmt::Debug for MobileCredentialValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("MobileCredentialValue([REDACTED])")
|
||||
}
|
||||
}
|
||||
fn rejected(detail: &str) -> MobileAuthenticationFfiError {
|
||||
MobileAuthenticationFfiError::Failed {
|
||||
kind: crate::MobileAuthenticationErrorKind::Entry,
|
||||
title: "Credential Request Rejected".into(),
|
||||
detail: detail.into(),
|
||||
}
|
||||
}
|
||||
#[uniffi::export]
|
||||
pub fn mobile_credential_form(
|
||||
fields: Vec<MobileCredentialField>,
|
||||
) -> Result<MobileCredentialForm, MobileAuthenticationFfiError> {
|
||||
let fields: Vec<_> = fields
|
||||
.into_iter()
|
||||
.map(|field| storage::CredentialField {
|
||||
index: field.index,
|
||||
hints: field.hints,
|
||||
html_attributes: field
|
||||
.html_attributes
|
||||
.into_iter()
|
||||
.map(|attribute| (attribute.name, attribute.value))
|
||||
.collect(),
|
||||
input_type: field.input_type,
|
||||
focused: field.focused,
|
||||
editable: field.editable,
|
||||
})
|
||||
.collect();
|
||||
let form = storage::credential_form(&fields).map_err(rejected)?;
|
||||
Ok(MobileCredentialForm {
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
})
|
||||
}
|
||||
#[uniffi::export]
|
||||
impl MobileAuthentication {
|
||||
pub fn expire_credential_requests(&self) -> Result<(), MobileAuthenticationFfiError> {
|
||||
self.authentication
|
||||
.expire_credential_requests()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
pub fn begin_credential_request(
|
||||
&self,
|
||||
caller: MobileCredentialCaller,
|
||||
) -> Result<MobileProviderRequest, MobileAuthenticationFfiError> {
|
||||
Ok(self
|
||||
.authentication
|
||||
.begin_credential_request(caller.into())?
|
||||
.into())
|
||||
}
|
||||
pub fn credential_request(
|
||||
&self,
|
||||
token: String,
|
||||
caller: MobileCredentialCaller,
|
||||
query: String,
|
||||
) -> Result<MobileProviderRequest, MobileAuthenticationFfiError> {
|
||||
Ok(self
|
||||
.authentication
|
||||
.search_credential_request(&token, &caller.into(), &query)?
|
||||
.into())
|
||||
}
|
||||
pub fn cancel_credential_request(
|
||||
&self,
|
||||
token: String,
|
||||
) -> Result<(), MobileAuthenticationFfiError> {
|
||||
self.authentication
|
||||
.cancel_credential_request(&token)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
pub fn stage_credential_save(
|
||||
&self,
|
||||
token: String,
|
||||
caller: MobileCredentialCaller,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<(), MobileAuthenticationFfiError> {
|
||||
self.authentication
|
||||
.stage_credential_save(
|
||||
&token,
|
||||
&caller.into(),
|
||||
ironstorage::repository::SecretBytes::new(username.into_bytes()),
|
||||
ironstorage::repository::SecretBytes::new(password.into_bytes()),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
pub fn save_staged_credential(
|
||||
&self,
|
||||
token: String,
|
||||
caller: MobileCredentialCaller,
|
||||
path: String,
|
||||
consent: bool,
|
||||
overwrite: bool,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<(), MobileAuthenticationFfiError> {
|
||||
self.authentication
|
||||
.save_staged_credential(
|
||||
&token,
|
||||
&caller.into(),
|
||||
&path,
|
||||
consent,
|
||||
overwrite,
|
||||
passphrase.map(|text| ironstorage::repository::SecretBytes::new(text.into_bytes())),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
pub fn fill_credential(
|
||||
&self,
|
||||
token: String,
|
||||
caller: MobileCredentialCaller,
|
||||
path: String,
|
||||
consent: bool,
|
||||
passphrase: Option<String>,
|
||||
allowed_usernames: Vec<String>,
|
||||
) -> Result<MobileCredentialValue, MobileAuthenticationFfiError> {
|
||||
let value = self.authentication.fill_credential(
|
||||
&token,
|
||||
&caller.into(),
|
||||
&path,
|
||||
consent,
|
||||
passphrase.map(|text| ironstorage::repository::SecretBytes::new(text.into_bytes())),
|
||||
&allowed_usernames,
|
||||
)?;
|
||||
Ok(MobileCredentialValue {
|
||||
username: String::from_utf8(value.username.expose().to_vec())
|
||||
.map_err(|_| rejected("Invalid username text."))?,
|
||||
password: String::from_utf8(value.password.expose().to_vec())
|
||||
.map_err(|_| rejected("Invalid password text."))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn mobile_credential_privileged_callers() -> String {
|
||||
storage::privileged_callers_json()
|
||||
}
|
||||
@@ -3021,3 +3021,6 @@ impl MobileAuthentication {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
mod credentials;
|
||||
pub use credentials::*;
|
||||
Reference in new issue
Block a user