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")?;