use std::collections::HashMap;
use zbus::{blocking::Connection, fdo, Error as ZbusError};
use zbus_polkit::policykit1::{AuthorityProxyBlocking, AuthorizationResult, CheckAuthorizationFlags, Subject};
use crate::{Error, Result, Text};
pub(crate) type RawContext = ();
#[derive(Debug)]
pub(crate) struct Context;
impl Context {
#[inline]
pub(crate) fn new(_: RawContext) -> Self {
Self
}
pub(crate) fn authenticate<F>(
&self,
_message: Text,
policy: &Policy,
callback: F,
) -> Result<()>
where
F: Fn(Result<()>) + Send + 'static,
{
let action_id = policy.action_id.clone();
std::thread::Builder::new()
.name("robius-authentication-polkit".into())
.spawn(move || {
let res = do_polkit_check(&action_id);
callback(res);
})
.map_err(|_| Error::Unavailable)?;
Ok(())
}
}
pub(crate) fn get_system_connection() -> Result<Connection> {
Connection::system().map_err(|_| Error::Unavailable)
}
pub(crate) fn get_authority_proxy(
conn: &Connection,
) -> Result<AuthorityProxyBlocking<'_>> {
AuthorityProxyBlocking::new(conn).map_err(|_| Error::Unavailable)
}
fn do_polkit_check(action_id: &str) -> Result<()> {
let conn = get_system_connection()?;
let auth = get_authority_proxy(&conn)?;
let details = HashMap::new();
let subject = Subject::new_for_owner(std::process::id(), None, None)
.map_err(|_| Error::Unavailable)?;
let result: AuthorizationResult = auth
.check_authorization(
&subject,
action_id,
&details,
CheckAuthorizationFlags::AllowUserInteraction.into(),
"",
)
.map_err(|err| {
eprintln!("polkit check_authorization error: {:?}", err);
match err {
ZbusError::MethodError(name, _, _) => match name.as_str() {
"org.freedesktop.PolicyKit1.Error.Cancelled" => Error::UserCanceled,
"org.freedesktop.PolicyKit1.Error.NotAuthorized" => Error::Authentication,
"org.freedesktop.PolicyKit1.Error.NotSupported" => Error::Unavailable,
"org.freedesktop.PolicyKit1.Error.NoAgent" => Error::Unavailable,
"org.freedesktop.PolicyKit1.Error.Failed" => Error::Unavailable,
"org.freedesktop.DBus.Error.NoReply"
| "org.freedesktop.DBus.Error.Timeout"
| "org.freedesktop.DBus.Error.TimedOut"
| "org.freedesktop.DBus.Error.ServiceUnknown"
| "org.freedesktop.DBus.Error.NameHasNoOwner"
| "org.freedesktop.DBus.Error.NoServer"
| "org.freedesktop.DBus.Error.Disconnected" => Error::Unavailable,
_ => Error::Unknown,
},
ZbusError::FDO(fdo_err) => match *fdo_err {
fdo::Error::TimedOut(_) | fdo::Error::NoReply(_) => Error::Unavailable,
_ => Error::Unknown,
},
ZbusError::InputOutput(_) => Error::Unavailable,
_ => Error::Unknown,
}
})?;
if result.is_authorized {
return Ok(());
}
if result
.details
.get("polkit.dismissed")
.map(|v| !v.is_empty())
.unwrap_or(false)
{
return Err(Error::UserCanceled);
}
if result.is_challenge {
return Err(Error::Unavailable);
}
Err(Error::Authentication)
}
#[derive(Debug)]
pub struct Policy {
pub(crate) action_id: String,
allowed_action_ids: Vec<String>,
}
impl Policy {
#[inline]
pub(crate) fn set_action_id(&mut self, id: String) -> Result<()> {
if self.allowed_action_ids.iter().any(|allowed| allowed == &id) {
self.action_id = id;
Ok(())
} else {
Err(Error::InvalidActionId)
}
}
}
#[derive(Debug)]
pub(crate) struct PolicyBuilder {
action_ids: Option<Vec<String>>,
}
impl PolicyBuilder {
#[inline]
pub const fn new() -> Self {
Self { action_ids: None }
}
#[inline]
pub fn action_ids(self, ids: Vec<String>) -> Self {
Self { action_ids: Some(ids) }
}
#[inline]
pub const fn biometrics(self, _: Option<crate::BiometricStrength>) -> Self { self }
#[inline]
pub const fn password(self, _: bool) -> Self { self }
#[inline]
pub const fn companion(self, _: bool) -> Self { self }
#[inline]
pub const fn wrist_detection(self, _: bool) -> Self { self }
#[inline]
pub fn build(self) -> Option<Policy> {
let action_ids = self.action_ids?;
if action_ids.is_empty() {
return None;
}
let action_id = action_ids[0].clone();
Some(Policy {
action_id,
allowed_action_ids: action_ids,
})
}
}