Unify shell opacity and add native authentication and system controls
This commit is contained in:
1 parent
8832a9bf2e
commit
c8e89fb758
35 files changed
+1674
-133
No files matched your search
@@ -0,0 +1,172 @@
|
||||
mod prompt;
|
||||
mod session;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
};
|
||||
use zbus::zvariant::{OwnedValue, Value};
|
||||
|
||||
const AGENT_PATH: &str = "/dev/ferese/PolkitAgent";
|
||||
const AUTHORITY: &str = "org.freedesktop.PolicyKit1";
|
||||
const AUTHORITY_PATH: &str = "/org/freedesktop/PolicyKit1/Authority";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum PromptEvent {
|
||||
Start { message: String, user: String },
|
||||
Request { prompt: String, echo: bool },
|
||||
Info { text: String },
|
||||
Error { text: String },
|
||||
Response { value: String },
|
||||
Cancel,
|
||||
}
|
||||
|
||||
type Identity = (String, HashMap<String, OwnedValue>);
|
||||
|
||||
#[derive(Default)]
|
||||
struct Agent {
|
||||
active: Mutex<HashMap<String, Arc<AtomicBool>>>,
|
||||
}
|
||||
|
||||
#[zbus::interface(name = "org.freedesktop.PolicyKit1.AuthenticationAgent")]
|
||||
impl Agent {
|
||||
async fn begin_authentication(
|
||||
&self,
|
||||
_action_id: String,
|
||||
message: String,
|
||||
_icon_name: String,
|
||||
_details: HashMap<String, String>,
|
||||
cookie: String,
|
||||
identities: Vec<Identity>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let uid = choose_identity(&identities).ok_or_else(|| {
|
||||
zbus::fdo::Error::Failed("No supported authentication identity".into())
|
||||
})?;
|
||||
let cancelled = Arc::new(AtomicBool::new(false));
|
||||
|
||||
{
|
||||
let mut active = self.active.lock().unwrap();
|
||||
if active.contains_key(&cookie) {
|
||||
return Err(zbus::fdo::Error::Failed("Duplicate request".into()));
|
||||
}
|
||||
active.insert(cookie.clone(), cancelled.clone());
|
||||
}
|
||||
|
||||
let result = tokio::task::spawn_blocking({
|
||||
let cookie = cookie.clone();
|
||||
move || {
|
||||
session::authenticate(uid, message.chars().take(240).collect(), cookie, cancelled)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|error| zbus::fdo::Error::Failed(error.to_string()))?;
|
||||
|
||||
self.active.lock().unwrap().remove(&cookie);
|
||||
match result {
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err(zbus::fdo::Error::Failed("Authentication cancelled".into())),
|
||||
Err(error) => Err(zbus::fdo::Error::Failed(error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_authentication(&self, cookie: String) {
|
||||
if let Some(cancelled) = self.active.lock().unwrap().get(&cookie) {
|
||||
cancelled.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_identity(identities: &[Identity]) -> Option<u32> {
|
||||
let available: Vec<_> = identities
|
||||
.iter()
|
||||
.filter(|(kind, _)| kind == "unix-user")
|
||||
.filter_map(|(_, details)| {
|
||||
details
|
||||
.get("uid")
|
||||
.and_then(|uid| u32::try_from(uid.clone()).ok())
|
||||
.filter(|uid| *uid <= i32::MAX as u32)
|
||||
})
|
||||
.collect();
|
||||
let current = unsafe { libc::geteuid() };
|
||||
|
||||
available
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|uid| *uid == current)
|
||||
.or_else(|| available.first().copied())
|
||||
}
|
||||
|
||||
fn session_subject() -> Result<(String, HashMap<String, Value<'static>>), String> {
|
||||
let id = std::env::var("XDG_SESSION_ID")
|
||||
.map_err(|_| "XDG_SESSION_ID is required for polkit registration".to_owned())?;
|
||||
|
||||
if id.is_empty() {
|
||||
return Err("XDG_SESSION_ID is empty".into());
|
||||
}
|
||||
|
||||
let mut details = HashMap::new();
|
||||
details.insert("session-id".to_owned(), Value::from(id));
|
||||
|
||||
Ok(("unix-session".into(), details))
|
||||
}
|
||||
|
||||
async fn run_agent() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let subject = session_subject()?;
|
||||
let connection = zbus::connection::Builder::system()?
|
||||
.serve_at(AGENT_PATH, Agent::default())?
|
||||
.build()
|
||||
.await?;
|
||||
let authority = zbus::Proxy::new(&connection, AUTHORITY, AUTHORITY_PATH, AUTHORITY).await?;
|
||||
let locale = std::env::var("LANG").unwrap_or_default();
|
||||
|
||||
authority
|
||||
.call::<_, _, ()>(
|
||||
"RegisterAuthenticationAgent",
|
||||
&(subject, locale, AGENT_PATH),
|
||||
)
|
||||
.await?;
|
||||
std::future::pending::<()>().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
libc::prctl(libc::PR_SET_DUMPABLE, 0);
|
||||
}
|
||||
|
||||
match std::env::args().nth(1).as_deref() {
|
||||
None => tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(run_agent()),
|
||||
Some("--prompt") => prompt::run(),
|
||||
Some("--check") => session::check().map_err(Into::into),
|
||||
_ => Err("Usage: ferese-polkit-agent [--check]".into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn chooses_current_identity_when_available() {
|
||||
let current = unsafe { libc::geteuid() };
|
||||
let identity = |uid: u32| {
|
||||
(
|
||||
"unix-user".to_owned(),
|
||||
HashMap::from([("uid".to_owned(), OwnedValue::from(uid))]),
|
||||
)
|
||||
};
|
||||
assert_eq!(
|
||||
choose_identity(&[identity(0), identity(current)]),
|
||||
Some(current)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user