From 6d8041a59a9295c0c16bf7842e52aa82b48d7712 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 1 Oct 2026 08:34:38 +0100 Subject: [PATCH] Register native polkit agent for the login session --- docs/installation.md | 3 ++ shell/ferese-polkit-agent/src/main.rs | 62 +++++++++++++++++++++++- shell/ferese-polkit-agent/src/session.rs | 2 +- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 34c837a..7e8fe2e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -93,6 +93,9 @@ and `previous` links for switching between releases during updates and rollback. Your configuration, other desktop sessions and existing PAM policy stay in place, while Ferese starts its authentication agent and portal services together with the session. +The authentication agent registers for the login session, so requests from desktop +apps use the Ferese dialog. Remove other polkit agents from your Ferese autostart +configuration to avoid competing session registrations. ### Initial configuration diff --git a/shell/ferese-polkit-agent/src/main.rs b/shell/ferese-polkit-agent/src/main.rs index 2302bf2..b3bf68a 100644 --- a/shell/ferese-polkit-agent/src/main.rs +++ b/shell/ferese-polkit-agent/src/main.rs @@ -96,7 +96,7 @@ fn choose_identity(identities: &[Identity]) -> Option { .or_else(|| available.first().copied()) } -fn session_subject() -> Result<(String, HashMap>), String> { +fn process_subject() -> Result<(String, HashMap>), String> { let stat = std::fs::read_to_string("/proc/self/stat").map_err(|error| error.to_string())?; let suffix = stat .rsplit_once(") ") @@ -115,12 +115,53 @@ fn session_subject() -> Result<(String, HashMap>), String Ok(("unix-process".into(), details)) } +async fn session_subject( + connection: &zbus::Connection, +) -> Result<(String, HashMap>), Box> { + // Nested previews must not take over authentication for the host desktop. + if std::env::var("FERESE_SESSION_IMPORT_ENV").as_deref() == Ok("0") { + return process_subject().map_err(Into::into); + } + let manager = zbus::Proxy::new( + connection, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + ) + .await?; + let path: zbus::zvariant::OwnedObjectPath = match manager.call("GetSessionByPID", &(std::process::id(),)).await { + Ok(path) => path, + Err(error) => { + // A launcher in the user manager can be outside the session cgroup + // while retaining the login session's environment. + let id = std::env::var("XDG_SESSION_ID").map_err(|_| error)?; + manager.call("GetSession", &(id,)).await? + } + }; + let session = zbus::Proxy::new( + connection, + "org.freedesktop.login1", + path, + "org.freedesktop.login1.Session", + ) + .await?; + let id: String = session.get_property("Id").await?; + Ok(login_session_subject(id)) +} + +fn login_session_subject(id: String) -> (String, HashMap>) { + ( + "unix-session".into(), + HashMap::from([("session-id".into(), Value::from(id))]), + ) +} + async fn run_agent() -> Result<(), Box> { - let subject = session_subject()?; let connection = zbus::connection::Builder::system()? .serve_at(AGENT_PATH, Agent::default())? .build() .await?; + let subject = session_subject(&connection).await?; let authority = zbus::Proxy::new(&connection, AUTHORITY, AUTHORITY_PATH, AUTHORITY_INTERFACE).await?; let locale = std::env::var("LANG").unwrap_or_default(); @@ -152,6 +193,23 @@ fn main() -> Result<(), Box> { mod tests { use super::*; + #[test] + fn desktop_agent_registers_for_the_login_session() { + let (kind, details) = login_session_subject("test-session".into()); + assert_eq!(kind, "unix-session"); + assert_eq!(details.len(), 1); + assert_eq!(details["session-id"], Value::from("test-session")); + } + + #[test] + fn process_subject_has_required_identity_fields() { + let (kind, details) = process_subject().unwrap(); + assert_eq!(kind, "unix-process"); + assert_eq!(details["pid"], Value::from(std::process::id())); + assert_eq!(details["uid"], Value::from(unsafe { libc::geteuid() } as i32)); + assert!(u64::try_from(details["start-time"].clone()).unwrap() > 0); + } + #[test] fn chooses_current_identity_when_available() { let current = unsafe { libc::geteuid() }; diff --git a/shell/ferese-polkit-agent/src/session.rs b/shell/ferese-polkit-agent/src/session.rs index 39ad782..aa56289 100644 --- a/shell/ferese-polkit-agent/src/session.rs +++ b/shell/ferese-polkit-agent/src/session.rs @@ -88,7 +88,7 @@ fn prompt_process( .arg("--prompt") .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .stderr(Stdio::null()) + .stderr(Stdio::inherit()) .spawn() .map_err(|e| e.to_string())?; let stdin = child.stdin.take().ok_or("Prompt has no input")?;