Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contrib/xyz.iinuwa.credentials.CredentialManager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<arg name="request" type="a{sv}" direction="in"/>
<arg type="a{sv}" direction="out"/>
</method>
<method name="GetClientCapabilities">
<arg type="a{sv}" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Peer">
<method name="Ping">
Expand Down
16 changes: 10 additions & 6 deletions webext/add-on/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ function rcvFromContent(msg) {
// const isCrossOrigin = origin === topOrigin
// const isTopLevel = contentPort.sender.frameId === 0;


const serializedOptions = serializeRequest(options)

console.debug(options.publicKey.challenge)
console.debug("background script received options, passing onto native app")
nativePort.postMessage({ requestId, cmd, options: serializedOptions, origin, topOrigin })
if (options) {
const serializedOptions = serializeRequest(options)

console.debug(options.publicKey.challenge)
console.debug("background script received options, passing onto native app")
nativePort.postMessage({ requestId, cmd, options: serializedOptions, origin, topOrigin })
} else {
console.debug("background script received message without arguments, passing onto native app")
nativePort.postMessage({ requestId, cmd, origin, topOrigin })
}
}

function rcvFromNative(msg) {
Expand Down
12 changes: 12 additions & 0 deletions webext/add-on/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ exportFunction(createCredential, navigator.credentials, { defineAs: "create"})
exportFunction(getCredential, navigator.credentials, { defineAs: "get"})


if (window.PublicKeyCredential) {
console.log("overriding PublicKeyCredential.getClientCapabilities() in content script");
exportFunction(getClientCapabilities, PublicKeyCredential, { defineAs: "getClientCapabilities"})
}

function startRequest() {
const requestId = requestCounter++;
const {promise, resolve, reject } = window.Promise.withResolvers();
Expand Down Expand Up @@ -182,3 +187,10 @@ function getCredential(request) {
webauthnPort.postMessage({ requestId, cmd: 'get', options, })
return promise.then(cloneCredentialResponse)
};

function getClientCapabilities() {
console.log("forwarding getClientCapabilities call from content script to background script")
const { requestId, promise } = startRequest();
webauthnPort.postMessage({ requestId, cmd: 'getClientCapabilities', })
return promise.then((capabilities) => cloneInto(capabilities, window))
};
13 changes: 11 additions & 2 deletions webext/app/credential_manager_shim.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ async def run(cmd, options, origin, top_origin):

interface = proxy_object.get_interface(
'xyz.iinuwa.credentials.CredentialManagerUi1')
logging.debug(f"COnnected to interface at {interface.path}")
logging.debug(f"Connected to interface at {interface.path}")

if cmd == 'create':
if 'publicKey' in options:
Expand All @@ -355,6 +355,12 @@ async def run(cmd, options, origin, top_origin):
return await get_passkey(interface, options['publicKey'], origin, top_origin)
else:
raise Exception(f"Could not get unknown credential type: {options.keys()[0]}")
elif cmd == 'getClientCapabilities':
rsp = await interface.call_get_client_capabilities()
response = {}
for name, val in rsp.items():
response[name] = val.value
return response
else:
raise Exception(f"unknown cmd: {cmd}")

Expand All @@ -366,7 +372,10 @@ async def run(cmd, options, origin, top_origin):
request_id = receivedMessage['requestId']
try:
cmd = receivedMessage['cmd']
options = receivedMessage['options']

options = None
if 'options' in receivedMessage:
options = receivedMessage['options']
origin = receivedMessage['origin']
top_origin = receivedMessage['topOrigin']
loop = asyncio.get_event_loop()
Expand Down
1 change: 1 addition & 0 deletions xyz-iinuwa-credential-manager-portal-gtk/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
src/config.rs
tests/config/mod.rs
1 change: 1 addition & 0 deletions xyz-iinuwa-credential-manager-portal-gtk/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ endif
subdir('data')
subdir('po')
subdir('src')
subdir('tests')

gnome.post_install(
gtk_update_icon_cache: true,
Expand Down
28 changes: 28 additions & 0 deletions xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,20 @@ impl CredentialManager {
))
}
}

async fn get_client_capabilities(&self) -> fdo::Result<GetClientCapabilitiesResponse> {
Ok(GetClientCapabilitiesResponse {
conditional_create: false,
conditional_get: false,
hybrid_transport: false,
passkey_platform_authenticator: false,
user_verifying_platform_authenticator: false,
related_origins: false,
signal_all_accepted_credentials: false,
signal_current_user_details: false,
signal_unknown_credential: false,
})
}
}

async fn create_password(
Expand Down Expand Up @@ -838,6 +852,20 @@ impl From<GetPublicKeyCredentialResponse> for GetCredentialResponse {
}
}

#[derive(SerializeDict, Type)]
#[zvariant(signature = "dict", rename_all = "camelCase")]
pub struct GetClientCapabilitiesResponse {
conditional_create: bool,
conditional_get: bool,
hybrid_transport: bool,
passkey_platform_authenticator: bool,
user_verifying_platform_authenticator: bool,
related_origins: bool,
signal_all_accepted_credentials: bool,
signal_current_user_details: bool,
signal_unknown_credential: bool,
}

fn format_client_data_json(
op: Operation,
challenge: &str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ mod test {
PublicKeyCredentialParameters, PublicKeyCredentialType,
};

use super::sign_attestation;
use super::{create_attested_credential_data, create_authenticator_data, sign_attestation};

#[test]
fn test_attestation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub const SERVICE_DIR: &'static str = @SERVICE_DIR@;
pub const SERVICE_NAME: &'static str = "xyz.iinuwa.credentials.CredentialManagerUi";
pub const PATH: &'static str = "/xyz/iinuwa/credentials/CredentialManagerUi";
pub const INTERFACE: &'static str = "xyz.iinuwa.credentials.CredentialManagerUi1";
70 changes: 70 additions & 0 deletions xyz-iinuwa-credential-manager-portal-gtk/tests/dbus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
mod config;

use std::collections::HashMap;

use client::DbusClient;
use zbus::zvariant::Value;

#[test]
fn test_client_capabilities() {
let client = DbusClient::new();
let msg = client.call_method("GetClientCapabilities", &()).unwrap();
let body = msg.body();
let rsp: HashMap<String, Value> = body.deserialize().unwrap();

let capabilities = HashMap::from([
("conditionalCreate", false),
("conditionalGet", false),
("hybridTransport", false),
("passkeyPlatformAuthenticator", false),
("userVerifyingPlatformAuthenticator", false),
("relatedOrigins", false),
("signalAllAcceptedCredentials", false),
("signalCurrentUserDetails", false),
("signalUnknownCredential", false),
]);
for (key, expected) in capabilities.iter() {
let value: &Value = rsp.get(*key).unwrap();
assert_eq!(*expected, value.try_into().unwrap());
}
}

mod client {
use crate::config::{INTERFACE, PATH, SERVICE_DIR, SERVICE_NAME};
use gtk::gio::{TestDBus, TestDBusFlags};
use serde::Serialize;
use zbus::{blocking::Connection, zvariant::DynamicType, Message};

fn init_test_dbus() -> TestDBus {
let dbus = TestDBus::new(TestDBusFlags::NONE);

// assumes this runs in root of Cargo project.
let current_dir = std::env::current_dir().unwrap();
let service_dir = current_dir.join(SERVICE_DIR);
println!("{:?}", service_dir);
dbus.add_service_dir(service_dir.to_str().unwrap());

dbus.up();
dbus
}

pub(super) struct DbusClient {
_bus: TestDBus,
}

impl DbusClient {
pub fn new() -> Self {
Self {
_bus: init_test_dbus(),
}
}

pub fn call_method<B>(&self, method_name: &str, body: &B) -> zbus::Result<Message>
where
B: Serialize + DynamicType,
{
let connection = Connection::session().unwrap();
connection.call_method(Some(SERVICE_NAME), PATH, Some(INTERFACE), method_name, body)
}
}
}
43 changes: 43 additions & 0 deletions xyz-iinuwa-credential-manager-portal-gtk/tests/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
test_config = configuration_data()
test_config.set_quoted(
'SERVICE_DIR',
meson.project_build_root() / backend_executable_name / 'tests',
)
test_config.set(
'DBUS_EXECUTABLE',
meson.project_build_root() / backend_executable_name / 'src' / backend_executable_name,
)
configure_file(
input: 'config' / 'mod.rs.in',
output: 'config.rs',
configuration: test_config,
)

# Copy the config output to the source directory.
run_command(
'cp',
meson.project_build_root() / backend_executable_name / 'tests' / 'config.rs',
meson.project_source_root() / backend_executable_name / 'tests' / 'config' / 'mod.rs',
check: true,
)

configure_file(
input: 'services' / 'xyz.iinuwa.CredentialManagerUi.service.in',
output: 'xyz.iinuwa.CredentialManagerUi.service',
configuration: test_config,
)

test(
'cargo dbus tests',
cargo,
env: [cargo_env],
args: [
'test',
'--test', 'dbus',
'--no-fail-fast', cargo_options,
'--',
'--nocapture',
],
protocol: 'exitcode',
verbose: true,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[D-BUS Service]
Name=xyz.iinuwa.credentials.CredentialManagerUi
Exec=@DBUS_EXECUTABLE@
Loading