Skip to content

Commit a036a7e

Browse files
committed
wip: export window handle to d-bus
1 parent 43b683f commit a036a7e

File tree

6 files changed

+119
-68
lines changed

6 files changed

+119
-68
lines changed

credentialsd-common/src/server.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use serde::{
77
de::{DeserializeSeed, Error, Visitor},
88
};
99
use zvariant::{
10-
self, Array, DeserializeDict, DynamicDeserialize, LE, NoneValue, Optional, OwnedValue, SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields
10+
self, Array, DeserializeDict, DynamicDeserialize, LE, NoneValue, Optional, OwnedValue,
11+
SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields,
1112
};
1213

1314
use crate::model::{BackgroundEvent, Operation, RequestingApplication};
@@ -622,7 +623,7 @@ pub struct ViewRequest {
622623
pub requesting_app: RequestingApplication,
623624

624625
/// Client window handle.
625-
pub window_handle: WindowHandle,
626+
pub window_handle: Optional<WindowHandle>,
626627
}
627628

628629
#[derive(Type, PartialEq, Debug)]
@@ -643,33 +644,38 @@ impl NoneValue for WindowHandle {
643644
impl Serialize for WindowHandle {
644645
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
645646
where
646-
S: serde::Serializer {
647+
S: serde::Serializer,
648+
{
647649
serializer.serialize_str(&self.to_string())
648650
}
649651
}
650652

651-
impl <'de> Deserialize<'de> for WindowHandle {
653+
impl<'de> Deserialize<'de> for WindowHandle {
652654
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
653655
where
654-
D: serde::Deserializer<'de> {
655-
deserializer.deserialize_str(WindowHandleVisitor { })
656+
D: serde::Deserializer<'de>,
657+
{
658+
deserializer.deserialize_str(WindowHandleVisitor {})
656659
}
657660
}
658661

659662
struct WindowHandleVisitor;
660663

661-
impl <'de> Visitor<'de> for WindowHandleVisitor {
664+
impl<'de> Visitor<'de> for WindowHandleVisitor {
662665
type Value = WindowHandle;
663666

664667
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
665-
write!(f, "a window handle formatted as `<window system>:<handle value>`")
668+
write!(
669+
f,
670+
"a window handle formatted as `<window system>:<handle value>`"
671+
)
666672
}
667673

668674
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
669-
where
670-
E: serde::de::Error, {
671-
v.try_into().map_err(E::custom)
672-
675+
where
676+
E: serde::de::Error,
677+
{
678+
v.try_into().map_err(E::custom)
673679
}
674680
}
675681

@@ -686,8 +692,8 @@ impl TryFrom<&str> for WindowHandle {
686692

687693
fn try_from(value: &str) -> Result<Self, Self::Error> {
688694
match value.split_once(':') {
689-
Some(("x11", handle)) => { Ok(Self::X11(handle.to_string())) },
690-
Some(("wayland", xid)) => { Ok(Self::Wayland(xid.to_string())) },
695+
Some(("x11", handle)) => Ok(Self::X11(handle.to_string())),
696+
Some(("wayland", xid)) => Ok(Self::Wayland(xid.to_string())),
691697
Some((window_system, _)) => Err(format!("Unknown windowing system: {window_system}")),
692698
None => Err("Invalid window handle string format".to_string()),
693699
}

credentialsd-ui/Cargo.lock

Lines changed: 24 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

credentialsd-ui/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ authors = ["Isaiah Inuwa <[email protected]>", "Martin Sirringhaus <martin.
66
license = "LGPL-3.0-only"
77

88
[dependencies]
9-
ashpd = { version = "0.12.0", features = ["backend", "gtk4_wayland", "gtk4_x11", "wayland"] }
9+
ashpd = { version = "0.12.0", features = ["async-std", "backend", "gtk4_wayland", "gtk4_x11", "wayland"], default-features = false }
1010
async-std = { version = "1.13.1", features = ["unstable"] }
1111
credentialsd-common = { path = "../credentialsd-common" }
1212
futures-lite = "2.6.0"

credentialsd-ui/src/gui/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
pub mod view_model;
22

3-
use std::str::FromStr;
43
use std::thread;
54
use std::{sync::Arc, thread::JoinHandle};
65

7-
use ashpd::{WindowIdentifier, WindowIdentifierType};
6+
use ashpd::{WindowIdentifierType};
87
use async_std::{channel::Receiver, sync::Mutex as AsyncMutex};
98

109
use credentialsd_common::server::ViewRequest;
@@ -29,9 +28,13 @@ fn run_gui<F: FlowController + Send + Sync + 'static>(
2928
flow_controller: Arc<AsyncMutex<F>>,
3029
request: ViewRequest,
3130
) {
32-
let parent_window = WindowIdentifierType::from_str(&request.window_handle.to_string())
33-
.inspect_err(|err| tracing::warn!("Failed to parse parent window handle: {err}"))
34-
.ok();
31+
let parent_window: Option<WindowIdentifierType> =
32+
request.window_handle.as_ref().and_then(|h| {
33+
h.to_string()
34+
.parse()
35+
.inspect_err(|err| tracing::warn!("Failed to parse parent window handle: {err}"))
36+
.ok()
37+
});
3538

3639
let (tx_update, rx_update) = async_std::channel::unbounded::<ViewUpdate>();
3740
let (tx_event, rx_event) = async_std::channel::unbounded::<ViewEvent>();

demo_client/gui.py

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env python3
22
from contextlib import closing
3+
import functools
34
import json
45
import math
56
import os
@@ -18,8 +19,9 @@
1819
import gi
1920

2021
gi.require_version("Gtk", "4.0")
22+
gi.require_version("GdkWayland", "4.0")
2123
gi.require_version("Adw", "1")
22-
from gi.repository import Gio, GObject, Gtk, Adw # noqa: E402
24+
from gi.repository import GdkWayland, Gio, GObject, Gtk, Adw # noqa: E402
2325

2426
import webauthn # noqa: E402
2527
import util # noqa: E402
@@ -72,6 +74,9 @@ def on_register(self, *args):
7274
now = math.floor(time.time())
7375
cur = DB.cursor()
7476
username = self.username.get_text()
77+
if not username:
78+
print("Username is required")
79+
return
7580
cur.execute(
7681
"select user_id, user_handle from users where username = ?", (username,)
7782
)
@@ -87,34 +92,40 @@ def on_register(self, *args):
8792
)
8893
options = self._get_registration_options(user_handle, username)
8994
print(f"registration options: {options}")
90-
auth_data = create_passkey(INTERFACE, self.origin, self.origin, options)
91-
if not user_id:
92-
cur.execute(
93-
"insert into users (username, user_handle, created_time) values (?, ?, ?)",
94-
(username, user_handle, now),
95-
)
96-
user_id = cur.lastrowid
97-
params = {
98-
"user_handle": user_handle,
99-
"cred_id": auth_data.cred_id,
100-
"aaguid": str(uuid.UUID(bytes=bytes(auth_data.aaguid))),
101-
"sign_count": None if auth_data.sign_count == 0 else auth_data.sign_count,
102-
"backup_eligible": 1 if "BE" in auth_data.flags else 0,
103-
"backup_state": 1 if "BS" in auth_data.flags else 0,
104-
"uv_initialized": 1 if "UV" in auth_data.flags else 0,
105-
"cose_pub_key": auth_data.pub_key_bytes,
106-
"created_time": now,
107-
}
108-
109-
add_passkey_sql = """
110-
insert into user_passkeys
111-
(user_handle, cred_id, aaguid, sign_count, backup_eligible, backup_state, uv_initialized, cose_pub_key, created_time)
112-
values
113-
(:user_handle, :cred_id, :aaguid, :sign_count, :backup_eligible, :backup_state, :uv_initialized, :cose_pub_key, :created_time)
114-
"""
115-
cur.execute(add_passkey_sql, params)
116-
print("Added passkey")
117-
DB.commit()
95+
def cb(user_id, toplevel, handle):
96+
cur = DB.cursor()
97+
window_handle = "wayland:{handle}"
98+
auth_data = create_passkey(INTERFACE, window_handle, self.origin, self.origin, options)
99+
if not user_id:
100+
cur.execute(
101+
"insert into users (username, user_handle, created_time) values (?, ?, ?)",
102+
(username, user_handle, now),
103+
)
104+
user_id = cur.lastrowid
105+
params = {
106+
"user_handle": user_handle,
107+
"cred_id": auth_data.cred_id,
108+
"aaguid": str(uuid.UUID(bytes=bytes(auth_data.aaguid))),
109+
"sign_count": None if auth_data.sign_count == 0 else auth_data.sign_count,
110+
"backup_eligible": 1 if "BE" in auth_data.flags else 0,
111+
"backup_state": 1 if "BS" in auth_data.flags else 0,
112+
"uv_initialized": 1 if "UV" in auth_data.flags else 0,
113+
"cose_pub_key": auth_data.pub_key_bytes,
114+
"created_time": now,
115+
}
116+
117+
add_passkey_sql = """
118+
insert into user_passkeys
119+
(user_handle, cred_id, aaguid, sign_count, backup_eligible, backup_state, uv_initialized, cose_pub_key, created_time)
120+
values
121+
(:user_handle, :cred_id, :aaguid, :sign_count, :backup_eligible, :backup_state, :uv_initialized, :cose_pub_key, :created_time)
122+
"""
123+
cur.execute(add_passkey_sql, params)
124+
print("Added passkey")
125+
DB.commit()
126+
cur.close()
127+
toplevel = self.get_surface()
128+
toplevel.export_handle(functools.partial(cb, user_id))
118129
cur.close()
119130

120131
@Gtk.Template.Callback()
@@ -202,17 +213,27 @@ def retrieve_user_cred(
202213
return user_cred
203214
else:
204215
return None
216+
def cb(toplevel, window_handle):
217+
print(f"received window handle: {window_handle}")
218+
window_handle = f"wayland:{window_handle}"
219+
220+
auth_data = get_passkey(
221+
INTERFACE,
222+
window_handle,
223+
self.origin,
224+
self.origin,
225+
self.rp_id,
226+
cred_ids,
227+
retrieve_user_cred,
228+
)
229+
print("Received passkey:")
230+
pprint(auth_data)
205231

206-
auth_data = get_passkey(
207-
INTERFACE,
208-
self.origin,
209-
self.origin,
210-
self.rp_id,
211-
cred_ids,
212-
retrieve_user_cred,
213-
)
214-
print("Received passkey:")
215-
pprint(auth_data)
232+
toplevel = self.get_surface()
233+
print(type(toplevel))
234+
toplevel.export_handle(cb)
235+
print("Waiting for handle to complete")
236+
# event.wait()
216237

217238
@GObject.Property(type=Gtk.StringList)
218239
def uv_pref(self):
@@ -281,7 +302,7 @@ def on_activate(self, app):
281302

282303

283304
def create_passkey(
284-
interface: ProxyInterface, origin: str, top_origin: str, options: dict
305+
interface: ProxyInterface, window_handle: str, origin: str, top_origin: str, options: dict
285306
) -> webauthn.AuthenticatorData:
286307
is_same_origin = origin == top_origin
287308
print(
@@ -298,7 +319,7 @@ def create_passkey(
298319
"publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}),
299320
}
300321

301-
rsp = interface.call_create_credential_sync(req)
322+
rsp = interface.call_create_credential_sync([req, window_handle])
302323

303324
# print("Received response")
304325
# pprint(rsp)
@@ -313,7 +334,7 @@ def create_passkey(
313334
return webauthn.verify_create_response(response_json, options, origin)
314335

315336

316-
def get_passkey(interface, origin, top_origin, rp_id, cred_ids, cred_lookup_fn):
337+
def get_passkey(interface, window_handle, origin, top_origin, rp_id, cred_ids, cred_lookup_fn):
317338
is_same_origin = origin == top_origin
318339
options = {
319340
"challenge": util.b64_encode(secrets.token_bytes(16)),
@@ -337,7 +358,7 @@ def get_passkey(interface, origin, top_origin, rp_id, cred_ids, cred_lookup_fn):
337358
"publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}),
338359
}
339360

340-
rsp = interface.call_get_credential_sync(req)
361+
rsp = interface.call_get_credential_sync([req, window_handle])
341362
# print("Received response")
342363
# pprint(rsp)
343364
if rsp["type"].value != "public-key":

doc/xyz.iinuwa.credentialsd.Credentials.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
</interface>
1010
<interface name="xyz.iinuwa.credentialsd.Credentials1">
1111
<method name="CreateCredential">
12-
<arg name="request" type="a{sv}" direction="in"/>
12+
<arg name="request" type="(a{sv}s)" direction="in"/>
1313
<arg type="a{sv}" direction="out"/>
1414
</method>
1515
<method name="GetCredential">
16-
<arg name="request" type="a{sv}" direction="in"/>
16+
<arg name="request" type="(a{sv}s)" direction="in"/>
1717
<arg type="a{sv}" direction="out"/>
1818
</method>
1919
<method name="GetClientCapabilities">

0 commit comments

Comments
 (0)