use std::collections::HashMap; use thiserror::Error; use zbus::Connection; use zvariant::Value; #[derive(Debug, Error)] pub enum PolkitError { #[error("not authorized: {action}")] NotAuthorized { action: String }, #[error(transparent)] Bus(#[from] zbus::Error), } pub enum Polkit { Permissive, System(Connection), } impl Polkit { pub fn permissive() -> Self { Self::Permissive } pub async fn system(conn: Connection) -> Self { Self::System(conn) } /// Check whether `pid` may invoke `action`. Permissive always allows; system mode /// calls org.freedesktop.PolicyKit1.Authority.CheckAuthorization on the bus. pub async fn check(&self, action: &str, pid: u32) -> Result<(), PolkitError> { match self { Self::Permissive => Ok(()), Self::System(conn) => system_check(conn, action, pid).await, } } } async fn system_check(conn: &Connection, action: &str, pid: u32) -> Result<(), PolkitError> { // Subject = ("unix-process", { "pid": u32, "start-time": u64 }). // start-time 0 is accepted by polkit; it will look up /proc itself. let mut subject_details: HashMap<&str, Value<'_>> = HashMap::new(); subject_details.insert("pid", Value::U32(pid)); subject_details.insert("start-time", Value::U64(0)); let subject = ("unix-process", subject_details); let details: HashMap<&str, &str> = HashMap::new(); let flags: u32 = 0; // No interaction; we don't block on auth prompt here. let cancellation_id = ""; let proxy = zbus::Proxy::new( conn, "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", ) .await?; let (is_authorized, _is_challenge, _details): (bool, bool, HashMap) = proxy .call( "CheckAuthorization", &(subject, action, details, flags, cancellation_id), ) .await?; if is_authorized { Ok(()) } else { Err(PolkitError::NotAuthorized { action: action.to_string(), }) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn permissive_always_allows() { let p = Polkit::permissive(); p.check("io.dangerousthings.AuthForge.set-policy", 1234) .await .unwrap(); } }