diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36f4042..37b70fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,8 @@ jobs: curl git build-essential \ libgtk-4-dev gettext libdbus-1-dev libssl-dev libudev-dev \ libxml2-utils blueprint-compiler desktop-file-utils \ - python3-pip ninja-build libnfc-dev libpcsclite-dev + python3-pip ninja-build libnfc-dev libpcsclite-dev \ + libwayland-dev libx11-dev - name: Install Meson run: | # Newer version needed for --interactive flag needed below diff --git a/credentialsd-common/src/server.rs b/credentialsd-common/src/server.rs index aa708c1..7417370 100644 --- a/credentialsd-common/src/server.rs +++ b/credentialsd-common/src/server.rs @@ -1,12 +1,14 @@ //! Types for serializing across D-Bus instances +use std::fmt::Display; + use serde::{ Deserialize, Serialize, - de::{DeserializeSeed, Error}, + de::{DeserializeSeed, Error, Visitor}, }; use zvariant::{ - self, Array, DeserializeDict, DynamicDeserialize, LE, Optional, OwnedValue, SerializeDict, - Signature, Structure, StructureBuilder, Type, Value, signature::Fields, + self, Array, DeserializeDict, DynamicDeserialize, LE, NoneValue, Optional, OwnedValue, + SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields, }; use crate::model::{BackgroundEvent, Operation, RequestingApplication}; @@ -619,6 +621,92 @@ pub struct ViewRequest { pub id: RequestId, pub rp_id: String, pub requesting_app: RequestingApplication, + + /// Client window handle. + pub window_handle: Optional, +} + +#[derive(Type, PartialEq, Debug)] +#[zvariant(signature = "s")] +pub enum WindowHandle { + Wayland(String), + X11(String), +} + +impl NoneValue for WindowHandle { + type NoneType = String; + + fn null_value() -> Self::NoneType { + String::new() + } +} + +impl Serialize for WindowHandle { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for WindowHandle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(WindowHandleVisitor {}) + } +} + +struct WindowHandleVisitor; + +impl<'de> Visitor<'de> for WindowHandleVisitor { + type Value = WindowHandle; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "a window handle formatted as `:`" + ) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + v.try_into().map_err(E::custom) + } +} + +impl TryFrom for WindowHandle { + type Error = String; + + fn try_from(value: String) -> Result { + WindowHandle::try_from(value.as_ref()) + } +} + +impl TryFrom<&str> for WindowHandle { + type Error = String; + + fn try_from(value: &str) -> Result { + match value.split_once(':') { + Some(("x11", handle)) => Ok(Self::X11(handle.to_string())), + Some(("wayland", xid)) => Ok(Self::Wayland(xid.to_string())), + Some((window_system, _)) => Err(format!("Unknown windowing system: {window_system}")), + None => Err("Invalid window handle string format".to_string()), + } + } +} + +impl Display for WindowHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Wayland(handle) => write!(f, "wayland:{handle}"), + Self::X11(xid) => write!(f, "x11:{xid}"), + } + } } fn value_to_owned(value: &Value<'_>) -> OwnedValue { diff --git a/credentialsd-ui/Cargo.lock b/credentialsd-ui/Cargo.lock index 2396be5..0d4b53f 100644 --- a/credentialsd-ui/Cargo.lock +++ b/credentialsd-ui/Cargo.lock @@ -96,6 +96,32 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ashpd" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" +dependencies = [ + "async-fs", + "async-net", + "async-trait", + "enumflags2", + "futures-channel", + "futures-util", + "gdk4-wayland", + "gdk4-x11", + "glib", + "gtk4", + "rand 0.9.2", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + [[package]] name = "asn1-rs" version = "0.7.1" @@ -184,6 +210,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + [[package]] name = "async-global-executor" version = "2.4.1" @@ -228,6 +265,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.4.0" @@ -607,9 +655,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cairo-rs" -version = "0.20.12" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0" +checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ "bitflags 2.9.1", "cairo-sys-rs", @@ -619,9 +667,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b" +checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c" dependencies = [ "glib-sys", "libc", @@ -827,6 +875,7 @@ dependencies = [ name = "credentialsd-ui" version = "0.1.0" dependencies = [ + "ashpd", "async-std", "credentialsd-common", "futures-lite", @@ -1056,12 +1105,27 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dunce" version = "1.0.5" @@ -1230,6 +1294,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fragile" version = "2.0.1" @@ -1346,9 +1419,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c" +checksum = "debb0d39e3cdd84626edfd54d6e4a6ba2da9a0ef2e796e691c4e9f8646fda00c" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -1358,9 +1431,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" +checksum = "bd95ad50b9a3d2551e25dd4f6892aff0b772fe5372d84514e9d0583af60a0ce7" dependencies = [ "gio-sys", "glib-sys", @@ -1371,9 +1444,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" +checksum = "756564212bbe4a4ce05d88ffbd2582581ac6003832d0d32822d0825cca84bfbf" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1386,9 +1459,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" +checksum = "a6d4e5b3ccf591826a4adcc83f5f57b4e59d1925cb4bf620b0d645f79498b034" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1401,6 +1474,56 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gdk4-wayland" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb81f3e1e7d9dac156e91a9dc81be33d8c99338975254ed93ca9f1de5cb3168" +dependencies = [ + "gdk4", + "gdk4-wayland-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk4-wayland-sys" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d739aa4ae9a1abb15f01ab2d8b8dd14a7792104ffb60bbc09bb94d76b905cfeb" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4-x11" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd10920274b392ff87d5b51932bebe24e428f9a2c0e1540b9d9be92a497f265a" +dependencies = [ + "gdk4", + "gdk4-x11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdk4-x11-sys" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8885d7dbeb194e6be61f0c62403200ad0c2f1da6930f816214eaf7124e0287bd" +dependencies = [ + "gdk4-sys", + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1473,9 +1596,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" -version = "0.20.12" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -1490,22 +1613,22 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "glib" -version = "0.20.12" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ "bitflags 2.9.1", "futures-channel", @@ -1524,9 +1647,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.12" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -1537,9 +1660,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -1565,9 +1688,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -1576,9 +1699,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b" +checksum = "2730030ac9db663fd8bfe1e7093742c1cafb92db9c315c9417c29032341fe2f9" dependencies = [ "glib", "graphene-sys", @@ -1587,9 +1710,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea" +checksum = "915e32091ea9ad241e4b044af62b7351c2d68aeb24f489a0d7f37a0fc484fd93" dependencies = [ "glib-sys", "libc", @@ -1610,9 +1733,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" +checksum = "e755de9d8c5896c5beaa028b89e1969d067f1b9bf1511384ede971f5983aa153" dependencies = [ "cairo-rs", "gdk4", @@ -1625,9 +1748,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" +checksum = "7ce91472391146f482065f1041876d8f869057b195b95399414caa163d72f4f7" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1641,9 +1764,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.7" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6" +checksum = "acb21d53cfc6f7bfaf43549731c43b67ca47d87348d81c8cfc4dcdd44828e1a4" dependencies = [ "cairo-rs", "field-offset", @@ -1662,9 +1785,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.9.5" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" +checksum = "3ccfb5a14a3d941244815d5f8101fa12d4577b59cc47245778d8d907b0003e42" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1674,9 +1797,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +checksum = "842577fe5a1ee15d166cd3afe804ce0cab6173bc789ca32e21308834f20088dd" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1822,6 +1945,108 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.6" @@ -2065,6 +2290,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "locale_config" version = "0.3.0" @@ -2442,9 +2673,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.20.12" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c" +checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69" dependencies = [ "gio", "glib", @@ -2454,9 +2685,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.20.10" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa" +checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd" dependencies = [ "glib-sys", "gobject-sys", @@ -2527,6 +2758,12 @@ dependencies = [ "base64ct", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2603,6 +2840,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2691,6 +2937,15 @@ dependencies = [ "image", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.40" @@ -2956,6 +3211,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3405,6 +3666,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.47.0" @@ -3636,12 +3907,30 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.17.0" @@ -3780,6 +4069,66 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.0.8", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.9.1", + "rustix 1.0.8", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -4194,6 +4543,22 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "x509-parser" version = "0.17.0" @@ -4217,6 +4582,29 @@ version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + [[package]] name = "zbus" version = "5.9.0" @@ -4297,12 +4685,66 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "zvariant" version = "5.6.0" @@ -4312,6 +4754,7 @@ dependencies = [ "endi", "enumflags2", "serde", + "url", "winnow", "zvariant_derive", "zvariant_utils", diff --git a/credentialsd-ui/Cargo.toml b/credentialsd-ui/Cargo.toml index 7e9cce2..c964d66 100644 --- a/credentialsd-ui/Cargo.toml +++ b/credentialsd-ui/Cargo.toml @@ -6,11 +6,12 @@ authors = ["Isaiah Inuwa ", "Martin Sirringhaus ( flow_controller: Arc>, request: ViewRequest, ) { + let parent_window: Option = + request.window_handle.as_ref().and_then(|h| { + h.to_string() + .parse() + .inspect_err(|err| tracing::warn!("Failed to parse parent window handle: {err}")) + .ok() + }); + let (tx_update, rx_update) = async_std::channel::unbounded::(); let (tx_event, rx_event) = async_std::channel::unbounded::(); let event_loop = async_std::task::spawn(async move { @@ -43,7 +52,7 @@ fn run_gui( .await; }); - view_model::gtk::start_gtk_app(tx_event, rx_update); + view_model::gtk::start_gtk_app(parent_window, tx_event, rx_update); async_std::task::block_on(event_loop.cancel()); } diff --git a/credentialsd-ui/src/gui/view_model/gtk/application.rs b/credentialsd-ui/src/gui/view_model/gtk/application.rs index 7ffa2b6..78bf518 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/application.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/application.rs @@ -1,3 +1,4 @@ +use ashpd::WindowIdentifierType; use async_std::channel::{Receiver, Sender}; use tracing::{debug, info}; @@ -13,6 +14,7 @@ mod imp { use crate::gui::view_model::gtk::ModelState; use super::*; + use ashpd::WindowIdentifierType; use glib::{WeakRef, clone}; use std::{ cell::{OnceCell, RefCell}, @@ -23,6 +25,7 @@ mod imp { pub struct CredentialsUi { pub window: OnceCell>, + pub(super) parent_window: RefCell>, pub(super) tx: RefCell>>, pub(super) rx: RefCell>>, } @@ -48,11 +51,16 @@ mod imp { return; } - let tx = self.tx.take().expect("sender to be initiated"); - let rx = self.rx.take().expect("receiver to be initiated"); + let tx = self.tx.take().expect("sender to be initialized"); + let rx = self.rx.take().expect("receiver to be initialized"); let view_model = ViewModel::new(tx, rx); let vm2 = view_model.clone(); + let window = CredentialsUiWindow::new(&app, view_model); + if let Some(parent_window) = self.parent_window.borrow().as_ref() { + parent_window.set_parent_of(&window); + } + let window2 = window.clone(); vm2.clone().connect_completed_notify(move |vm| { if vm.completed() { @@ -157,7 +165,7 @@ impl CredentialsUi { ApplicationExtManual::run(self) } - pub(crate) fn new(tx: Sender, rx: Receiver) -> Self { + pub(crate) fn new(parent_window: Option, tx: Sender, rx: Receiver) -> Self { let app: Self = glib::Object::builder() .property("application-id", APP_ID) .property( @@ -165,6 +173,7 @@ impl CredentialsUi { "/xyz/iinuwa/credentialsd/CredentialUI/", ) .build(); + app.imp().parent_window.replace(parent_window); app.imp().tx.replace(Some(tx)); app.imp().rx.replace(Some(rx)); app diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index aa783c9..0b64265 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -3,6 +3,7 @@ pub mod credential; pub mod device; mod window; +use ashpd::WindowIdentifierType; use async_std::channel::{Receiver, Sender}; use gettextrs::{LocaleCategory, gettext, ngettext}; use glib::clone; @@ -369,6 +370,7 @@ impl ViewModel { } pub fn start_gtk_app( + parent_window: Option, tx_event: async_std::channel::Sender, rx_update: async_std::channel::Receiver, ) { @@ -385,7 +387,7 @@ pub fn start_gtk_app( let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file"); gio::resources_register(&res); - let app = CredentialsUi::new(tx_event, rx_update); + let app = CredentialsUi::new(parent_window, tx_event, rx_update); app.run(); } diff --git a/credentialsd-ui/src/gui/view_model/gtk/window.rs b/credentialsd-ui/src/gui/view_model/gtk/window.rs index de92ecb..98164c5 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/window.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/window.rs @@ -1,13 +1,12 @@ use std::cell::RefCell; -use glib::Properties; -use gtk::gdk::Texture; +use gtk::{gdk, gio, glib}; + use gtk::subclass::prelude::*; + +use gdk::Texture; +use glib::{clone, Properties}; use gtk::{Picture, prelude::*}; -use gtk::{ - gio, - glib::{self, clone}, -}; use super::application::CredentialsUi; use super::{ViewModel, device::DeviceObject}; @@ -15,8 +14,6 @@ use crate::config::{APP_ID, PROFILE}; use crate::gui::view_model::Transport; mod imp { - use gtk::Picture; - use crate::gui::view_model::ViewEvent; use super::*; @@ -134,7 +131,7 @@ mod imp { glib::wrapper! { pub struct CredentialsUiWindow(ObjectSubclass) @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, - @implements gio::ActionMap, gio::ActionGroup, gtk::Root; + @implements gtk::Accessible, gio::ActionMap, gio::ActionGroup, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; } diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index dd80bd0..9eaf9aa 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -24,7 +24,7 @@ use credentialsd_common::{ CredentialRequest, CredentialResponse, Device, Error as CredentialServiceError, Operation, RequestingApplication, Transport, }, - server::{RequestId, ViewRequest}, + server::{RequestId, ViewRequest, WindowHandle}, }; use crate::credential_service::{hybrid::HybridEvent, usb::UsbEvent}; @@ -101,6 +101,7 @@ impl< &self, request: &CredentialRequest, requesting_app: Option, + window_handle: Option, tx: Sender>, ) { let request_id = { @@ -135,6 +136,7 @@ impl< id: request_id, rp_id, requesting_app: requesting_app.unwrap_or_default(), // We can't send Options, so we send an empty string instead, if we don't know the peer + window_handle: window_handle.into() }; let launch_ui_response = self @@ -434,7 +436,7 @@ mod test { cred_service .lock() .await - .init_request(&request, None, request_tx) + .init_request(&request, None, None, request_tx) .await; user.request_hybrid_credential().await; tokio::time::timeout(Duration::from_secs(5), request_rx) diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index d7e561e..d74c6ea 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -8,7 +8,7 @@ use credentialsd_common::model::{ BackgroundEvent, CredentialRequest, CredentialResponse, Error as CredentialServiceError, RequestingApplication, WebAuthnError, }; -use credentialsd_common::server::{Device, RequestId}; +use credentialsd_common::server::{Device, RequestId, WindowHandle}; use futures_lite::StreamExt; use tokio::sync::oneshot; use tokio::{ @@ -46,6 +46,7 @@ pub async fn start_flow_control_service< Sender<( CredentialRequest, Option, // Application name sending the request + Option, // Client window handle oneshot::Sender>, )>, )> { @@ -70,10 +71,10 @@ pub async fn start_flow_control_service< let (initiator_tx, mut initiator_rx) = mpsc::channel(2); tokio::spawn(async move { let svc = svc2; - while let Some((msg, requesting_app, tx)) = initiator_rx.recv().await { + while let Some((msg, requesting_app, window_handle, tx)) = initiator_rx.recv().await { svc.lock() .await - .init_request(&msg, requesting_app, tx) + .init_request(&msg, requesting_app, window_handle, tx) .await; } }); @@ -370,6 +371,7 @@ pub trait CredentialRequestController { &self, requesting_app: Option, request: CredentialRequest, + window_handle: Option, ) -> impl Future> + Send; } @@ -377,6 +379,7 @@ pub struct CredentialRequestControllerClient { pub initiator: Sender<( CredentialRequest, Option, // Application name sending the request + Option, // Client window handle, oneshot::Sender>, )>, } @@ -386,10 +389,11 @@ impl CredentialRequestController for CredentialRequestControllerClient { &self, requesting_app: Option, request: CredentialRequest, + window_handle: Option, ) -> Result { let (tx, rx) = oneshot::channel(); self.initiator - .send((request, requesting_app, tx)) + .send((request, requesting_app, window_handle, tx)) .await .unwrap(); let response = rx.await.map_err(|_| { diff --git a/credentialsd/src/dbus/gateway.rs b/credentialsd/src/dbus/gateway.rs index ff698f1..816a340 100644 --- a/credentialsd/src/dbus/gateway.rs +++ b/credentialsd/src/dbus/gateway.rs @@ -10,15 +10,12 @@ use credentialsd_common::{ }, server::{ CreateCredentialRequest, CreateCredentialResponse, GetCredentialRequest, - GetCredentialResponse, + GetCredentialResponse, WindowHandle, }, }; use tokio::sync::Mutex as AsyncMutex; use zbus::{ - fdo, interface, - message::Header, - names::{BusName, UniqueName}, - Connection, DBusError, + Connection, DBusError, fdo, interface, message::Header, names::{BusName, UniqueName}, zvariant::Optional }; use crate::dbus::{ @@ -220,6 +217,7 @@ impl CredentialGateway, #[zbus(connection)] connection: &Connection, + parent_window: Optional, request: CreateCredentialRequest, ) -> Result { let (_origin, is_same_origin, _top_origin) = @@ -255,7 +253,7 @@ impl CredentialGateway CredentialGateway, #[zbus(connection)] connection: &Connection, + parent_window: Optional, request: GetCredentialRequest, ) -> Result { let (_origin, is_same_origin, _top_origin) = @@ -319,7 +318,7 @@ impl CredentialGateway webauthn.AuthenticatorData: is_same_origin = origin == top_origin print( @@ -298,7 +319,7 @@ def create_passkey( "publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}), } - rsp = interface.call_create_credential_sync(req) + rsp = interface.call_create_credential_sync([window_handle, req]) # print("Received response") # pprint(rsp) @@ -313,7 +334,7 @@ def create_passkey( return webauthn.verify_create_response(response_json, options, origin) -def get_passkey(interface, origin, top_origin, rp_id, cred_ids, cred_lookup_fn): +def get_passkey(interface, window_handle, origin, top_origin, rp_id, cred_ids, cred_lookup_fn): is_same_origin = origin == top_origin options = { "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): "publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}), } - rsp = interface.call_get_credential_sync(req) + rsp = interface.call_get_credential_sync([window_handle, req]) # print("Received response") # pprint(rsp) if rsp["type"].value != "public-key": diff --git a/demo_client/main.py b/demo_client/main.py index 3a9cebf..166a6b4 100755 --- a/demo_client/main.py +++ b/demo_client/main.py @@ -114,7 +114,7 @@ async def create_password(interface): }, ), } - rsp = await interface.call_create_credential(password_req) + rsp = await interface.call_create_credential(["", password_req]) return rsp @@ -131,7 +131,7 @@ async def get_password(interface): ], ), } - rsp = await interface.call_get_credential(password_req) + rsp = await interface.call_get_credential(["", password_req]) if rsp["type"].value == "password": cred = rsp["password"].value id = cred["id"].value @@ -174,7 +174,7 @@ async def create_passkey(interface, origin, top_origin, rp_id, user_handle, user "publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}), } - rsp = await interface.call_create_credential(req) + rsp = await interface.call_create_credential(["", req]) print("Received response") pprint(rsp) @@ -215,7 +215,7 @@ async def get_passkey( "publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}), } - rsp = await interface.call_get_credential(req) + rsp = await interface.call_get_credential(["", req]) print("Received response") pprint(rsp) if rsp["type"].value != "public-key": diff --git a/demo_client/window.blp b/demo_client/window.blp index 78755ab..95e12c0 100644 --- a/demo_client/window.blp +++ b/demo_client/window.blp @@ -4,7 +4,7 @@ using Adw 1; template $MyAppWindow: ApplicationWindow { default-width: 600; default-height: 300; - title: _("Hello, Blueprint!"); + title: _("WebAuthn Test Client"); Box { orientation: vertical; diff --git a/doc/api.md b/doc/api.md index 757d4ed..ecebc68 100644 --- a/doc/api.md +++ b/doc/api.md @@ -153,6 +153,21 @@ it would pass this request to this API: } ``` +## Window Identifiers + +For window identifiers, we follow the same format as the +[XDG Desktop Portal conventions for window identifiers][xdg-window-identifiers]. + +Where a `parent_window` is specified, the value should be a string in the format: + +`:` + +The supported window systems are `wayland` and `x11`. + +If the client does not have a window or cannot access it, pass an empty string. + +[xdg-window-identifiers]: https://flatpak.github.io/xdg-desktop-portal/docs/window-identifiers.html + # Gateway API The Gateway is the entrypoint for public clients to retrieve and store @@ -174,14 +189,19 @@ for what kind of credential the client would like to create. ### Request ``` -CreateCredentialRequest[a{sv}] { - origin: string - is_same_origin: string - type: CredentialType - -} +CreateCredentialRequest( + IN parent_window s, + IN options a{sv} { + origin: string + is_same_origin: string + type: CredentialType + + } +) ``` +For information on `parent_window`, see [Window Identifiers](#window-identifiers). + > TODO: We should make this a tagged enum ``` @@ -287,13 +307,18 @@ credentials the client will accept. ### Request ``` -GetCredentialRequest[a{sv}] { - origin: string - is_same_origin: string - publicKey: GetPublicKeyCredentialOptions? -} +GetCredentialRequest ( + IN parent_window s + IN options a{sv} { + origin: string + is_same_origin: string + publicKey: GetPublicKeyCredentialOptions? + } +) ``` +For information on `parent_window`, see [Window Identifiers](#window-identifiers). + Note that while only one credential type can be specified in `CreateCredential()`, credential types in this `GetCredential()` are not mutually exclusive: as new credential types are added to the specification, a client may diff --git a/doc/xyz.iinuwa.credentialsd.Credentials.xml b/doc/xyz.iinuwa.credentialsd.Credentials.xml index 19b16b7..be1cac7 100644 --- a/doc/xyz.iinuwa.credentialsd.Credentials.xml +++ b/doc/xyz.iinuwa.credentialsd.Credentials.xml @@ -9,11 +9,11 @@ - + - + diff --git a/webext/app/credential_manager_shim.py b/webext/app/credential_manager_shim.py index f4167a7..ace3ba2 100755 --- a/webext/app/credential_manager_shim.py +++ b/webext/app/credential_manager_shim.py @@ -300,7 +300,7 @@ async def create_passkey(interface, options, origin, top_origin): "publicKey": Variant("a{sv}", {"request_json": Variant("s", req_json)}), } logging.debug("Sending request to D-Bus API") - rsp = await interface.call_create_credential(req) + rsp = await interface.call_create_credential(["", req]) if rsp["type"].value != "public-key": raise Exception( f"Invalid credential type received: expected 'public-key', received {rsp['type'.value]}" @@ -335,7 +335,7 @@ async def get_passkey(interface, options, origin, top_origin): } logging.debug("Sending request to D-Bus API") - rsp = await interface.call_get_credential(req) + rsp = await interface.call_get_credential(["", req]) if rsp["type"].value != "public-key": raise Exception( f"Invalid credential type received: expected 'public-key', received {rsp['type'.value]}"