Skip to content

Commit 63ec9a0

Browse files
authored
Merge pull request #17 from msirringhaus/get_client_capability
Implement getClientCapabilities
2 parents bdf2fbd + fa21704 commit 63ec9a0

12 files changed

Lines changed: 187 additions & 9 deletions

File tree

contrib/xyz.iinuwa.credentials.CredentialManager.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
<arg name="request" type="a{sv}" direction="in"/>
1717
<arg type="a{sv}" direction="out"/>
1818
</method>
19+
<method name="GetClientCapabilities">
20+
<arg type="a{sv}" direction="out"/>
21+
</method>
1922
</interface>
2023
<interface name="org.freedesktop.DBus.Peer">
2124
<method name="Ping">

webext/add-on/background.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ function rcvFromContent(msg) {
3636
// const isCrossOrigin = origin === topOrigin
3737
// const isTopLevel = contentPort.sender.frameId === 0;
3838

39-
40-
const serializedOptions = serializeRequest(options)
41-
42-
console.debug(options.publicKey.challenge)
43-
console.debug("background script received options, passing onto native app")
44-
nativePort.postMessage({ requestId, cmd, options: serializedOptions, origin, topOrigin })
39+
if (options) {
40+
const serializedOptions = serializeRequest(options)
41+
42+
console.debug(options.publicKey.challenge)
43+
console.debug("background script received options, passing onto native app")
44+
nativePort.postMessage({ requestId, cmd, options: serializedOptions, origin, topOrigin })
45+
} else {
46+
console.debug("background script received message without arguments, passing onto native app")
47+
nativePort.postMessage({ requestId, cmd, origin, topOrigin })
48+
}
4549
}
4650

4751
function rcvFromNative(msg) {

webext/add-on/content.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ exportFunction(createCredential, navigator.credentials, { defineAs: "create"})
1414
exportFunction(getCredential, navigator.credentials, { defineAs: "get"})
1515

1616

17+
if (window.PublicKeyCredential) {
18+
console.log("overriding PublicKeyCredential.getClientCapabilities() in content script");
19+
exportFunction(getClientCapabilities, PublicKeyCredential, { defineAs: "getClientCapabilities"})
20+
}
21+
1722
function startRequest() {
1823
const requestId = requestCounter++;
1924
const {promise, resolve, reject } = window.Promise.withResolvers();
@@ -182,3 +187,10 @@ function getCredential(request) {
182187
webauthnPort.postMessage({ requestId, cmd: 'get', options, })
183188
return promise.then(cloneCredentialResponse)
184189
};
190+
191+
function getClientCapabilities() {
192+
console.log("forwarding getClientCapabilities call from content script to background script")
193+
const { requestId, promise } = startRequest();
194+
webauthnPort.postMessage({ requestId, cmd: 'getClientCapabilities', })
195+
return promise.then((capabilities) => cloneInto(capabilities, window))
196+
};

webext/app/credential_manager_shim.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ async def run(cmd, options, origin, top_origin):
343343

344344
interface = proxy_object.get_interface(
345345
'xyz.iinuwa.credentials.CredentialManagerUi1')
346-
logging.debug(f"COnnected to interface at {interface.path}")
346+
logging.debug(f"Connected to interface at {interface.path}")
347347

348348
if cmd == 'create':
349349
if 'publicKey' in options:
@@ -355,6 +355,12 @@ async def run(cmd, options, origin, top_origin):
355355
return await get_passkey(interface, options['publicKey'], origin, top_origin)
356356
else:
357357
raise Exception(f"Could not get unknown credential type: {options.keys()[0]}")
358+
elif cmd == 'getClientCapabilities':
359+
rsp = await interface.call_get_client_capabilities()
360+
response = {}
361+
for name, val in rsp.items():
362+
response[name] = val.value
363+
return response
358364
else:
359365
raise Exception(f"unknown cmd: {cmd}")
360366

@@ -366,7 +372,10 @@ async def run(cmd, options, origin, top_origin):
366372
request_id = receivedMessage['requestId']
367373
try:
368374
cmd = receivedMessage['cmd']
369-
options = receivedMessage['options']
375+
376+
options = None
377+
if 'options' in receivedMessage:
378+
options = receivedMessage['options']
370379
origin = receivedMessage['origin']
371380
top_origin = receivedMessage['topOrigin']
372381
loop = asyncio.get_event_loop()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
src/config.rs
2+
tests/config/mod.rs

xyz-iinuwa-credential-manager-portal-gtk/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ endif
6565
subdir('data')
6666
subdir('po')
6767
subdir('src')
68+
subdir('tests')
6869

6970
gnome.post_install(
7071
gtk_update_icon_cache: true,

xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,20 @@ impl CredentialManager {
241241
))
242242
}
243243
}
244+
245+
async fn get_client_capabilities(&self) -> fdo::Result<GetClientCapabilitiesResponse> {
246+
Ok(GetClientCapabilitiesResponse {
247+
conditional_create: false,
248+
conditional_get: false,
249+
hybrid_transport: false,
250+
passkey_platform_authenticator: false,
251+
user_verifying_platform_authenticator: false,
252+
related_origins: false,
253+
signal_all_accepted_credentials: false,
254+
signal_current_user_details: false,
255+
signal_unknown_credential: false,
256+
})
257+
}
244258
}
245259

246260
async fn create_password(
@@ -838,6 +852,20 @@ impl From<GetPublicKeyCredentialResponse> for GetCredentialResponse {
838852
}
839853
}
840854

855+
#[derive(SerializeDict, Type)]
856+
#[zvariant(signature = "dict", rename_all = "camelCase")]
857+
pub struct GetClientCapabilitiesResponse {
858+
conditional_create: bool,
859+
conditional_get: bool,
860+
hybrid_transport: bool,
861+
passkey_platform_authenticator: bool,
862+
user_verifying_platform_authenticator: bool,
863+
related_origins: bool,
864+
signal_all_accepted_credentials: bool,
865+
signal_current_user_details: bool,
866+
signal_unknown_credential: bool,
867+
}
868+
841869
fn format_client_data_json(
842870
op: Operation,
843871
challenge: &str,

xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ mod test {
715715
PublicKeyCredentialParameters, PublicKeyCredentialType,
716716
};
717717

718-
use super::sign_attestation;
718+
use super::{create_attested_credential_data, create_authenticator_data, sign_attestation};
719719

720720
#[test]
721721
fn test_attestation() {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub const SERVICE_DIR: &'static str = @SERVICE_DIR@;
2+
pub const SERVICE_NAME: &'static str = "xyz.iinuwa.credentials.CredentialManagerUi";
3+
pub const PATH: &'static str = "/xyz/iinuwa/credentials/CredentialManagerUi";
4+
pub const INTERFACE: &'static str = "xyz.iinuwa.credentials.CredentialManagerUi1";
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
mod config;
2+
3+
use std::collections::HashMap;
4+
5+
use client::DbusClient;
6+
use zbus::zvariant::Value;
7+
8+
#[test]
9+
fn test_client_capabilities() {
10+
let client = DbusClient::new();
11+
let msg = client.call_method("GetClientCapabilities", &()).unwrap();
12+
let body = msg.body();
13+
let rsp: HashMap<String, Value> = body.deserialize().unwrap();
14+
15+
let capabilities = HashMap::from([
16+
("conditionalCreate", false),
17+
("conditionalGet", false),
18+
("hybridTransport", false),
19+
("passkeyPlatformAuthenticator", false),
20+
("userVerifyingPlatformAuthenticator", false),
21+
("relatedOrigins", false),
22+
("signalAllAcceptedCredentials", false),
23+
("signalCurrentUserDetails", false),
24+
("signalUnknownCredential", false),
25+
]);
26+
for (key, expected) in capabilities.iter() {
27+
let value: &Value = rsp.get(*key).unwrap();
28+
assert_eq!(*expected, value.try_into().unwrap());
29+
}
30+
}
31+
32+
mod client {
33+
use crate::config::{INTERFACE, PATH, SERVICE_DIR, SERVICE_NAME};
34+
use gtk::gio::{TestDBus, TestDBusFlags};
35+
use serde::Serialize;
36+
use zbus::{blocking::Connection, zvariant::DynamicType, Message};
37+
38+
fn init_test_dbus() -> TestDBus {
39+
let dbus = TestDBus::new(TestDBusFlags::NONE);
40+
41+
// assumes this runs in root of Cargo project.
42+
let current_dir = std::env::current_dir().unwrap();
43+
let service_dir = current_dir.join(SERVICE_DIR);
44+
println!("{:?}", service_dir);
45+
dbus.add_service_dir(service_dir.to_str().unwrap());
46+
47+
dbus.up();
48+
dbus
49+
}
50+
51+
pub(super) struct DbusClient {
52+
_bus: TestDBus,
53+
}
54+
55+
impl DbusClient {
56+
pub fn new() -> Self {
57+
Self {
58+
_bus: init_test_dbus(),
59+
}
60+
}
61+
62+
pub fn call_method<B>(&self, method_name: &str, body: &B) -> zbus::Result<Message>
63+
where
64+
B: Serialize + DynamicType,
65+
{
66+
let connection = Connection::session().unwrap();
67+
connection.call_method(Some(SERVICE_NAME), PATH, Some(INTERFACE), method_name, body)
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)