git.delta.rocks / remowt / refs/commits / d819a0103251

difftreelog

feat functional polkit prompt

pvnwrxrnYaroslav Bolyukin2024-08-06parent: #2499daa.patch.diff
in: trunk

7 files changed

modifiedCargo.lockdiffbeforeafterboth
883name = "polkit-shared"883name = "polkit-shared"
884version = "0.1.0"884version = "0.1.0"
885dependencies = [885dependencies = [
886 "nix",
886 "serde",887 "serde",
887 "zbus",888 "zbus",
888]889]
modifiedcmds/polkit-backend/src/main.rsdiffbeforeafterboth
45 .prompt_text(45 .prompt_text(
46 echo,46 echo,
47 &prompt.to_string_lossy(),47 &prompt.to_string_lossy(),
48 "Polkit prompt request",48 "PAM prompt request",
49 &[],49 &[],
50 )50 )
51 .map_err(|e| {51 .map_err(|e| {
81 let prompt = prompt.to_string_lossy();81 let prompt = prompt.to_string_lossy();
82 let result = self82 let result = self
83 .083 .0
84 .prompt_radio(&prompt, "Polkit prompt request", &[])84 .prompt_radio(&prompt, "PAM prompt request", &[])
85 .map_err(|_| ErrorCode::CONV_ERR)?;85 .map_err(|_| ErrorCode::CONV_ERR)?;
86 Ok(result)86 Ok(result)
87 }87 }
modifiedcmds/remowt-agent/src/main.rsdiffbeforeafterboth
1use std::borrow::Cow;
1use std::collections::HashMap;2use std::collections::{BTreeMap, HashMap};
2use std::io::{stdout, Write};3use std::io::{stdout, Write};
3use std::marker::PhantomData;4use std::marker::PhantomData;
4use std::sync::{Mutex, RwLock};5use std::sync::{Mutex, RwLock};
5use std::{future, process};6use std::{future, process};
67
7use clap::Parser;8use clap::Parser;
8use polkit_shared::{BackendRequest, Identity};9use polkit_shared::{emphasize, BackendRequest, Identity, PidDisplay};
9use tokio::runtime::Handle;10use tokio::runtime::Handle;
10use tokio::task::{AbortHandle, JoinHandle, LocalSet};11use tokio::task::{AbortHandle, JoinHandle, LocalSet};
11use tracing::trace;12use tracing::trace;
12use ui_prompt::dbus::DbusPrompterInterface;13use ui_prompt::dbus::DbusPrompterInterface;
13use ui_prompt::rofi::RofiPrompter;14use ui_prompt::rofi::RofiPrompter;
14use ui_prompt::Prompter;15use ui_prompt::{PrependSourcePrompter, Prompter, Source};
15use zbus::zvariant::{OwnedValue, Str};16use zbus::zvariant::{OwnedValue, Str};
16use zbus::ObjectServer;17use zbus::{fdo, ObjectServer};
17use zbus::{interface, proxy, Connection};18use zbus::{interface, proxy, Connection};
18use zbus_polkit::policykit1::Subject;19use zbus_polkit::policykit1::Subject;
1920
81 action_id: String,82 action_id: String,
82 message: String,83 message: String,
83 icon_name: String,84 icon_name: String,
84 details: HashMap<String, String>,85 mut details: BTreeMap<String, String>,
85 cookie: String,86 cookie: String,
86 identities: Vec<Identity>,87 identities: Vec<Identity>,
87 ) -> zbus::fdo::Result<()> {88 ) -> zbus::fdo::Result<()> {
89 use std::fmt::Write;
88 trace!("begin auth");90 trace!("begin auth");
89 let task = {91 let task = {
90 let connection = self.connection.clone();92 let connection = self.connection.clone();
91 let helper = self.helper.clone();93 let helper = self.helper.clone();
92 let cookie = cookie.clone();94 let cookie = cookie.clone();
93 tokio::task::spawn(async move {95 tokio::task::spawn(async move {
94 trace!("conversation task");96 trace!("conversation task");
97 let mut description = format!("{message}\n\n<b>Action id:</b> {action_id}",);
98 if let Some(subject) = details.remove("polkit.caller-pid") {
99 let _ = write!(description, "\n<b>Caller:</b> ");
100 if let Ok(pid) = subject.parse::<u32>() {
101 let _ = write!(description, "{}", PidDisplay(pid));
102 } else {
103 let _ = write!(description, "{}", emphasize("invalid pid"));
104 }
105 }
106 if let Some(subject) = details.remove("polkit.subject-pid") {
107 let _ = write!(description, "\n<b>Subject:</b> ");
108 if let Ok(pid) = subject.parse::<u32>() {
109 let _ = write!(description, "{}", PidDisplay(pid));
110 } else {
111 let _ = write!(description, "{}", emphasize("invalid pid"));
112 }
113 }
114 let mut prompter = PrependSourcePrompter {
115 source: vec![Source(Cow::Borrowed("polkit agent"))],
116 description: description.clone(),
117 prompter: RofiPrompter,
118 };
119
120 let identity_displays: Vec<String> =
121 identities.iter().map(|v| v.to_string()).collect();
122 let identity_displays: Vec<&str> =
123 identity_displays.iter().map(|v| v.as_str()).collect();
124 let choosen_identity = match identity_displays.len() {
125 0 => {
126 return Err(fdo::Error::AuthFailed(
127 "no identity to authenticate as".to_owned(),
128 ))
129 }
130 1 => 0,
131 _ => {
132 prompter
133 .prompt_enum(
134 "Identity",
135 "Select identity to use for polkit authorization",
136 &identity_displays,
137 &[],
138 )
139 .await?
140 }
141 };
142
143 let _ = write!(
144 description,
145 "\n<b>Identity:</b> {}",
146 identities[choosen_identity as usize]
147 );
148 prompter.description = description;
149
150 prompter.source.push(Source(Cow::Borrowed("polkit daemon")));
95 let prompter = TemporaryPrompterInterface::new(connection, RofiPrompter).await;151 let prompter = TemporaryPrompterInterface::new(connection, prompter).await;
96 helper152 helper
97 .init_conversation(153 .init_conversation(
98 BackendRequest {154 BackendRequest {
99 cookie: cookie.to_owned(),155 cookie: cookie.to_owned(),
100 environment: HashMap::new(),156 environment: HashMap::new(),
101 prompter_path: prompter.path.clone(),157 prompter_path: prompter.path.clone(),
102 // TODO: Let user choose158 // TODO: Let user choose
103 identity: identities.get(0).expect("first always exists").clone(),159 identity: identities[choosen_identity as usize].clone(),
104 }, // cookie.to_owned(), HashMap::new(), prompter.path.clone()160 }, // cookie.to_owned(), HashMap::new(), prompter.path.clone()
105 )161 )
106 .await?;162 .await?;
135 }191 }
136}192}
137193
138const OBJ_PATH: &str = "/0lach/polkitAgent";194const OBJ_PATH: &str = "/org/freedesktop/PolicyKit1/AuthenticationAgent";
139195
140#[proxy(196#[proxy(
141 interface = "lach.PolkitHelper",197 interface = "lach.PolkitHelper",
modifiedcrates/polkit-shared/Cargo.tomldiffbeforeafterboth
4edition = "2021"4edition = "2021"
55
6[dependencies]6[dependencies]
7nix = "0.29.0"
7serde = { version = "1.0.204", features = ["derive"] }8serde = { version = "1.0.204", features = ["derive"] }
8zbus = "4.4.0"9zbus = "4.4.0"
910
modifiedcrates/polkit-shared/src/lib.rsdiffbeforeafterboth
1use std::collections::HashMap;1use std::collections::HashMap;
22use std::{fmt, fs};
3
4use nix::unistd::{Uid, User};
3use serde::{Deserialize, Serialize};5use serde::{Deserialize, Serialize};
4use zbus::zvariant::{OwnedValue, Type};6use zbus::zvariant::{OwnedValue, Type, Value};
7
8pub fn emphasize(s: impl AsRef<str>) -> String {
9 format!("<span style=\"italic\">&lt;{}&gt;</span>", escape(s),)
10}
11fn command(s: impl AsRef<str>) -> String {
12 format!("<u><tt>{}</tt></u>", s.as_ref())
13}
14fn escape(s: impl AsRef<str>) -> String {
15 s.as_ref()
16 .replace("&", "&quot;")
17 .replace("<", "&lt;")
18 .replace(">", "&gt;")
19}
20
21pub struct PidDisplay(pub u32);
22impl fmt::Display for PidDisplay {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 if self.0 == 1 {
25 emphasize("init").fmt(f)
26 } else if let Ok(proc) = fs::read_to_string(format!("/proc/{}/cmdline", self.0)) {
27 write!(
28 f,
29 "<sub>command</sub>{}",
30 command(
31 proc.replace("\0", " ")
32 .strip_suffix(" ")
33 .expect("cmdline should end with NUL")
34 )
35 )
36 } else if let Ok(proc) = fs::read_to_string(format!("/proc/{}/comm", self.0)) {
37 write!(f, "<sub>process</sub>{}", command(proc.replace("\0", " ")))
38 } else {
39 emphasize("unknown process").fmt(f)
40 }
41 }
42}
543
6#[derive(Serialize, Deserialize, Type, PartialEq, Debug)]44#[derive(Serialize, Deserialize, Type, PartialEq, Debug)]
7pub struct Identity {45pub struct Identity {
8 pub kind: String,46 pub kind: String,
9 pub details: HashMap<String, OwnedValue>,47 pub details: HashMap<String, OwnedValue>,
10}48}
49
50impl fmt::Display for Identity {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self.kind.as_str() {
53 "unix-user" => match self.details.get("uid").map(|v| &**v) {
54 Some(Value::U32(uid)) => match User::from_uid(Uid::from_raw(*uid)) {
55 Ok(Some(u)) => write!(
56 f,
57 "<sub>user</sub>{}<sup>{}</sup>{}",
58 u.name,
59 u.uid,
60 if u.gecos.is_empty() {
61 "".to_owned()
62 } else {
63 format!(": {}", escape(u.gecos.to_string_lossy()))
64 }
65 ),
66 Ok(None) => emphasize("not found").fmt(f),
67 Err(e) => {
68 let user = format!("could not get user: {e}");
69 emphasize(&user).fmt(f)?;
70 Ok(())
71 }
72 },
73
74 _ => emphasize("unknown uid").fmt(f),
75 },
76 _ => emphasize(format!("identity of unknown kind: {}", self.kind)).fmt(f),
77 }
78 }
79}
1180
12impl Clone for Identity {81impl Clone for Identity {
13 fn clone(&self) -> Self {82 fn clone(&self) -> Self {
modifiedcrates/ui-prompt/src/lib.rsdiffbeforeafterboth
1818
19#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]19#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]
20#[derive(serde::Serialize, serde::Deserialize, Clone)]20#[derive(serde::Serialize, serde::Deserialize, Clone)]
21pub struct Source(Cow<'static, str>);21pub struct Source(pub Cow<'static, str>);
22impl fmt::Display for Source {22impl fmt::Display for Source {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "<u>{}</u>", self.0)24 write!(f, "<u>{}</u>", self.0)
79}79}
8080
81pub struct PrependSourcePrompter<P> {81pub struct PrependSourcePrompter<P> {
82 prompter: P,82 pub prompter: P,
83 source: Vec<Source>,83 pub source: Vec<Source>,
84 pub description: String,
84}85}
85impl<P> PrependSourcePrompter<P> {86impl<P> PrependSourcePrompter<P> {
86 fn source(&self, input: &[Source]) -> Vec<Source> {87 fn source(&self, input: &[Source]) -> Vec<Source> {
87 let mut out = self.source.clone();88 let mut out = self.source.clone();
88 out.extend(input.iter().cloned());89 out.extend(input.iter().cloned());
89 out90 out
90 }91 }
92 fn description(&self, input: &str) -> String {
93 if self.description.is_empty() {
94 input.to_owned()
95 } else if input.is_empty() {
96 self.description.to_owned()
97 } else {
98 format!("{input}\n\n{}", self.description)
99 }
100 }
91}101}
92impl<P> Prompter for PrependSourcePrompter<P>102impl<P> Prompter for PrependSourcePrompter<P>
93where103where
94 P: Prompter + Sync,104 P: Prompter + Sync,
95{105{
106 async fn prompt_radio(
107 &self,
108 prompt: &str,
109 description: &str,
110 source: &[Source],
111 ) -> Result<bool> {
112 self.prompter
113 .prompt_radio(prompt, &self.description(description), &self.source(source))
114 .await
115 }
116
96 async fn prompt_enum(117 async fn prompt_enum(
97 &self,118 &self,
103 self.prompter124 self.prompter
104 .prompt_enum(prompt, description, variants, &self.source(source))125 .prompt_enum(
126 prompt,
127 dbg!(&self.description(description)),
128 variants,
129 &self.source(source),
130 )
105 .await131 .await
106 }132 }
116 .prompt_text(echo, prompt, description, &self.source(source))142 .prompt_text(
143 echo,
144 prompt,
145 &self.description(description),
146 &self.source(source),
147 )
117 .await148 .await
118 }149 }
119150
120 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {151 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
121 self.prompter152 self.prompter
122 .display_text(error, description, &self.source(source))153 .display_text(error, &self.description(description), &self.source(source))
123 .await154 .await
124 }155 }
125}156}
modifiedcrates/ui-prompt/src/rofi.rsdiffbeforeafterboth
27 description.to_owned()27 description.to_owned()
28 } else {28 } else {
29 let mut out = format!("{description}\n\n<b>Requested on ",);29 let mut out = format!("{description}\n\n<b>Requested on ",);
30 for s in source.iter() {30 for (i, s) in source.iter().enumerate() {
31 if i != 0 {
32 out.push_str(" -> ");
33 }
31 out.push_str(&s.to_string());34 out.push_str(&s.to_string());
32 }35 }
33 out.push_str("</b>");36 out.push_str("</b>");
43 fixup_prompt(prompt),46 fixup_prompt(prompt),
44 "-format",47 "-format",
45 "i",48 "i",
49 "-markup-rows",
46 ]);50 ]);
47 cmd.stdin(Stdio::piped());51 cmd.stdin(Stdio::piped());
48 cmd.stdout(Stdio::piped());52 cmd.stdout(Stdio::piped());
100 description.to_owned()104 description.to_owned()
101 } else {105 } else {
102 let mut out = format!("{description}\n\n<b>Requested on ",);106 let mut out = format!("{description}\n\n<b>Requested on ",);
103 for s in source.iter() {107 for (i, s) in source.iter().enumerate() {
108 if i != 0 {
109 out.push_str(" -> ");
110 }
104 out.push_str(&s.to_string());111 out.push_str(&s.to_string());
105 }112 }
106 out.push_str("</b>");113 out.push_str("</b>");