Skip to content

Commit 69d9374

Browse files
committed
Add prompt implementation for service method lock
Even though gnome-keyring-daemon doesn't implement/show a prompt during the service method lock. The secret service spec says a prompt can be used during lock. So, we're adding this to the new daemon. Signed-off-by: Dhanuka Warusadura <dhanuka@gnome.org>
1 parent 085a3da commit 69d9374

File tree

4 files changed

+418
-14
lines changed

4 files changed

+418
-14
lines changed

server/src/gnome/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
mod secret_exchange;
1+
pub mod prompter;
2+
pub mod secret_exchange;

server/src/gnome/prompter.rs

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// org.gnome.keyring.Prompter
2+
// https://gitlab.gnome.org/GNOME/gcr/-/blob/main/gcr/org.gnome.keyring.Prompter.xml
3+
4+
use clap::error::Result;
5+
use oo7::dbus::ServiceError;
6+
use serde::{Deserialize, Serialize};
7+
use zbus::{
8+
interface, proxy,
9+
zvariant::{DeserializeDict, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value},
10+
};
11+
12+
use crate::{
13+
gnome::secret_exchange::SecretExchange,
14+
prompt::{Prompt, PromptRole},
15+
service::Service,
16+
};
17+
18+
#[derive(Debug, DeserializeDict, SerializeDict, Type)]
19+
#[zvariant(signature = "a{sv}")]
20+
// System prompt properties
21+
pub struct Properties {
22+
title: Option<String>,
23+
#[zvariant(rename = "choice-label")]
24+
choice_label: Option<String>,
25+
description: Option<String>,
26+
message: Option<String>,
27+
#[zvariant(rename = "caller-window")]
28+
caller_window: Option<String>,
29+
warning: Option<String>,
30+
#[zvariant(rename = "password-new")]
31+
password_new: Option<bool>,
32+
#[zvariant(rename = "password-strength")]
33+
password_strength: Option<u32>,
34+
#[zvariant(rename = "choice-chosen")]
35+
choice_chosen: Option<bool>,
36+
#[zvariant(rename = "continue-label")]
37+
continue_label: Option<String>,
38+
#[zvariant(rename = "cancel-label")]
39+
cancel_label: Option<String>,
40+
}
41+
42+
impl Properties {
43+
fn for_lock() -> Self {
44+
Self {
45+
title: Some("Lock Keyring".to_string()),
46+
choice_label: None,
47+
description: Some("Confirm locking 'login' Keyring".to_string()),
48+
message: Some("Lock Keyring".to_string()),
49+
caller_window: None,
50+
warning: None,
51+
password_new: Some(false),
52+
password_strength: Some(0),
53+
choice_chosen: Some(false),
54+
continue_label: Some("Lock".to_string()),
55+
cancel_label: Some("Cancel".to_string()),
56+
}
57+
}
58+
}
59+
60+
#[derive(Debug, Type)]
61+
#[zvariant(signature = "s")]
62+
// Possible values for PromptReady reply parameter
63+
pub enum Reply {
64+
No,
65+
Yes,
66+
Empty,
67+
}
68+
69+
const NO: &str = "no";
70+
const YES: &str = "yes";
71+
const EMPTY: &str = "";
72+
73+
impl Serialize for Reply {
74+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75+
where
76+
S: serde::Serializer,
77+
{
78+
match self {
79+
Self::No => str::serialize(NO, serializer),
80+
Self::Yes => str::serialize(YES, serializer),
81+
Self::Empty => str::serialize(EMPTY, serializer),
82+
}
83+
}
84+
}
85+
86+
impl<'de> Deserialize<'de> for Reply {
87+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88+
where
89+
D: serde::Deserializer<'de>,
90+
{
91+
match String::deserialize(deserializer)?.as_str() {
92+
NO => Ok(Self::No),
93+
YES => Ok(Self::Yes),
94+
EMPTY => Ok(Self::Empty),
95+
err => Err(serde::de::Error::custom(format!("Invalid reply {err}"))),
96+
}
97+
}
98+
}
99+
100+
#[derive(Deserialize, Serialize, Debug, Type)]
101+
#[serde(rename_all = "lowercase")]
102+
#[zvariant(signature = "s")]
103+
// Possible values for PerformPrompt type parameter
104+
pub enum PromptType {
105+
Confirm,
106+
Password,
107+
}
108+
109+
// org.gnome.keyring.internal.Prompter
110+
111+
#[proxy(
112+
default_service = "org.gnome.keyring.SystemPrompter",
113+
interface = "org.gnome.keyring.internal.Prompter",
114+
default_path = "/org/gnome/keyring/Prompter"
115+
)]
116+
pub trait Prompter {
117+
fn begin_prompting(&self, callback: &OwnedObjectPath) -> Result<(), ServiceError>;
118+
119+
fn perform_prompt(
120+
&self,
121+
callback: OwnedObjectPath,
122+
type_: PromptType,
123+
properties: Properties,
124+
exchange: &str,
125+
) -> Result<(), ServiceError>;
126+
127+
fn stop_prompting(&self, callback: OwnedObjectPath) -> Result<(), ServiceError>;
128+
}
129+
130+
// org.gnome.keyring.internal.Prompter.Callback
131+
132+
pub struct PrompterCallback {
133+
service: Service,
134+
path: OwnedObjectPath,
135+
}
136+
137+
#[interface(name = "org.gnome.keyring.internal.Prompter.Callback")]
138+
impl PrompterCallback {
139+
pub async fn prompt_ready(
140+
&self,
141+
reply: Reply,
142+
_properties: Properties,
143+
exchange: &str,
144+
#[zbus(connection)] connection: &zbus::Connection,
145+
) -> Result<(), ServiceError> {
146+
let Some(prompt) = self.service.prompt().await else {
147+
return Err(ServiceError::NoSuchObject(
148+
"Prompt does not exist.".to_string(),
149+
));
150+
};
151+
152+
match prompt.role() {
153+
PromptRole::Lock => {
154+
match reply {
155+
Reply::Empty => {
156+
// First PromptReady call
157+
let secret_exchange = SecretExchange::new().map_err(|err| {
158+
ServiceError::ZBus(zbus::Error::FDO(Box::new(
159+
zbus::fdo::Error::Failed(format!(
160+
"Failed to generate SecretExchange {err}."
161+
)),
162+
)))
163+
})?;
164+
let exchange = secret_exchange.begin();
165+
166+
let properties = Properties::for_lock();
167+
let path = self.path.clone();
168+
169+
tokio::spawn(PrompterCallback::perform_prompt(
170+
connection.clone(),
171+
path,
172+
PromptType::Confirm,
173+
properties,
174+
exchange,
175+
));
176+
}
177+
Reply::No => {
178+
// Second PromptReady call and the prompt is dismissed
179+
tracing::debug!("Prompt is being dismissed.");
180+
181+
tokio::spawn(PrompterCallback::stop_prompting(
182+
connection.clone(),
183+
self.path.clone(),
184+
));
185+
186+
let signal_emitter = self.service.signal_emitter(prompt.path().clone())?;
187+
let result = Value::new::<Vec<OwnedObjectPath>>(vec![])
188+
.try_to_owned()
189+
.unwrap();
190+
191+
tokio::spawn(PrompterCallback::prompt_completed(
192+
signal_emitter,
193+
true,
194+
result,
195+
));
196+
}
197+
Reply::Yes => {
198+
// Second PromptReady call with the final exchange
199+
let service = self.service.clone();
200+
let objects = prompt.objects().clone();
201+
let result = Value::new(&objects).try_to_owned().unwrap();
202+
203+
tokio::spawn(async move {
204+
let _ = service.set_locked(true, &objects, true).await;
205+
});
206+
207+
tokio::spawn(PrompterCallback::stop_prompting(
208+
connection.clone(),
209+
self.path.clone(),
210+
));
211+
212+
let signal_emitter = self.service.signal_emitter(prompt.path().clone())?;
213+
214+
tokio::spawn(PrompterCallback::prompt_completed(
215+
signal_emitter,
216+
false,
217+
result,
218+
));
219+
}
220+
}
221+
}
222+
PromptRole::Unlock => todo!(),
223+
PromptRole::CreateCollection => todo!(),
224+
};
225+
226+
Ok(())
227+
}
228+
229+
pub async fn prompt_done(
230+
&self,
231+
#[zbus(object_server)] object_server: &zbus::ObjectServer,
232+
) -> Result<(), ServiceError> {
233+
if let Some(prompt) = self.service.prompt().await {
234+
object_server.remove::<Prompt, _>(prompt.path()).await?;
235+
self.service.remove_prompt().await;
236+
}
237+
object_server.remove::<Self, _>(&self.path).await?;
238+
239+
Ok(())
240+
}
241+
}
242+
243+
impl PrompterCallback {
244+
pub async fn new(service: Service) -> Self {
245+
let index = service.prompt_index().await;
246+
Self {
247+
path: OwnedObjectPath::try_from(format!("/org/gnome/keyring/Prompt/p{index}")).unwrap(),
248+
service,
249+
}
250+
}
251+
252+
pub fn path(&self) -> &OwnedObjectPath {
253+
&self.path
254+
}
255+
256+
pub async fn perform_prompt(
257+
connection: zbus::Connection,
258+
path: OwnedObjectPath,
259+
prompt_type: PromptType,
260+
properties: Properties,
261+
exchange: String,
262+
) -> Result<(), ServiceError> {
263+
let prompter = PrompterProxy::new(&connection).await?;
264+
prompter
265+
.perform_prompt(path, prompt_type, properties, &exchange)
266+
.await?;
267+
268+
Ok(())
269+
}
270+
271+
pub async fn stop_prompting(
272+
connection: zbus::Connection,
273+
path: OwnedObjectPath,
274+
) -> Result<(), ServiceError> {
275+
let prompter = PrompterProxy::new(&connection).await?;
276+
prompter.stop_prompting(path).await?;
277+
278+
Ok(())
279+
}
280+
281+
pub async fn prompt_completed(
282+
signal_emitter: zbus::object_server::SignalEmitter<'_>,
283+
dismissed: bool,
284+
result: OwnedValue,
285+
) -> Result<(), ServiceError> {
286+
Prompt::completed(&signal_emitter, dismissed, result).await?;
287+
tracing::debug!("Prompt completed.");
288+
289+
Ok(())
290+
}
291+
}

server/src/prompt.rs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,95 @@
11
// org.freedesktop.Secret.Prompt
22

33
use oo7::dbus::ServiceError;
4-
use zbus::{interface, object_server::SignalEmitter, zvariant::OwnedValue};
4+
use zbus::{
5+
interface,
6+
object_server::SignalEmitter,
7+
zvariant::{OwnedObjectPath, OwnedValue},
8+
};
59

6-
#[derive(Debug)]
7-
pub struct Prompt {}
10+
use crate::{
11+
gnome::prompter::{PrompterCallback, PrompterProxy},
12+
service::Service,
13+
};
14+
15+
#[derive(Debug, Clone)]
16+
#[allow(unused)]
17+
pub enum PromptRole {
18+
Lock,
19+
Unlock,
20+
CreateCollection,
21+
}
22+
23+
#[derive(Debug, Clone)]
24+
pub struct Prompt {
25+
service: Service,
26+
objects: Vec<OwnedObjectPath>,
27+
role: PromptRole,
28+
path: OwnedObjectPath,
29+
}
830

931
#[interface(name = "org.freedesktop.Secret.Prompt")]
1032
impl Prompt {
11-
pub async fn prompt(&self, _window_id: &str) -> Result<(), ServiceError> {
12-
todo!()
33+
pub async fn prompt(
34+
&self,
35+
_window_id: &str,
36+
#[zbus(connection)] connection: &zbus::Connection,
37+
#[zbus(object_server)] object_server: &zbus::ObjectServer,
38+
) -> Result<(), ServiceError> {
39+
let callback = PrompterCallback::new(self.service.clone()).await;
40+
let path = callback.path().clone();
41+
let connection = connection.clone();
42+
43+
object_server.at(&path, callback).await?;
44+
tracing::debug!("Prompt `{}` created.", self.path);
45+
46+
tokio::spawn(async move {
47+
let prompter = PrompterProxy::new(&connection).await.unwrap();
48+
prompter.begin_prompting(&path).await.unwrap();
49+
});
50+
51+
Ok(())
1352
}
1453

15-
pub async fn dismiss(&self) -> Result<(), ServiceError> {
16-
todo!()
54+
pub async fn dismiss(
55+
&self,
56+
#[zbus(object_server)] object_server: &zbus::ObjectServer,
57+
) -> Result<(), ServiceError> {
58+
object_server.remove::<Self, _>(&self.path).await?;
59+
self.service.remove_prompt().await;
60+
61+
Ok(())
1762
}
1863

1964
#[zbus(signal, name = "Completed")]
20-
async fn completed(
65+
pub async fn completed(
2166
signal_emitter: &SignalEmitter<'_>,
2267
dismissed: bool,
2368
result: OwnedValue,
2469
) -> zbus::Result<()>;
2570
}
71+
72+
impl Prompt {
73+
pub async fn new(service: Service, objects: Vec<OwnedObjectPath>, role: PromptRole) -> Self {
74+
let index = service.prompt_index().await;
75+
Self {
76+
path: OwnedObjectPath::try_from(format!("/org/freedesktop/secrets/prompt/p{index}"))
77+
.unwrap(),
78+
service,
79+
objects,
80+
role,
81+
}
82+
}
83+
84+
pub fn path(&self) -> &OwnedObjectPath {
85+
&self.path
86+
}
87+
88+
pub fn role(&self) -> &PromptRole {
89+
&self.role
90+
}
91+
92+
pub fn objects(&self) -> &Vec<OwnedObjectPath> {
93+
&self.objects
94+
}
95+
}

0 commit comments

Comments
 (0)