Bundles plan tasks 1.8 (read methods), 1.9 (EnrollOwn/RemoveOwn with polkit gate), 1.10 (EnrollOther, SetPolicy, pending, recovery-code), and 1.11 (main.rs system-bus registration) — they land together because Polkit::System is only constructed by main.rs, so splitting them mid-implementation would require dead_code allows that immediately reverse. Adds: - daemon/src/dbus.rs — AuthForge struct + #[zbus::interface] impl with all 9 methods. Reads (ListCredentials, GetPolicy) are unauthenticated; writes call authz() which dispatches to polkit. Includes 9 integration tests via a tokio::net::UnixStream::pair p2p connection — no system bus needed for tests. - daemon/src/main.rs — connects to system bus, picks Polkit::system or Polkit::permissive based on AUTHFORGE_POLKIT_BYPASS env var, registers the AuthForge interface at /io/dangerousthings/AuthForge, requests well-known name io.dangerousthings.AuthForge, then parks forever. - daemon/src/polkit.rs — drop dead_code allows now that System is wired. - daemon/src/state.rs — gate has_pending() behind cfg(test); production reads go through the on-disk file in later phases, not this in-memory cache. - common/src/types.rs (formatting only via rustfmt). Tests: 14/14 daemon tests pass (4 state, 1 polkit, 9 dbus). 7/7 common tests pass. cargo clippy --workspace --all-targets -D warnings clean. cargo fmt clean.
86 lines
2.3 KiB
Rust
86 lines
2.3 KiB
Rust
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<String, String>) = 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();
|
|
}
|
|
}
|