From df223580512bb2c262b50a9fcaf3e5a65defc310 Mon Sep 17 00:00:00 2001 From: michael Date: Sun, 26 Apr 2026 20:49:21 -0700 Subject: [PATCH] feat(daemon): add polkit authorizer with permissive + system modes --- daemon/src/main.rs | 1 + daemon/src/polkit.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 daemon/src/polkit.rs diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 04b168a..713afe5 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Result; use tracing::info; +mod polkit; mod state; #[tokio::main] diff --git a/daemon/src/polkit.rs b/daemon/src/polkit.rs new file mode 100644 index 0000000..09eb589 --- /dev/null +++ b/daemon/src/polkit.rs @@ -0,0 +1,80 @@ +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), +} + +#[allow(dead_code)] // wired up in Task 1.8. +pub enum Polkit { + Permissive, + System(Connection), +} + +#[allow(dead_code)] // wired up in Task 1.8. +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(); + } +}