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
369 changes: 3 additions & 366 deletions creds-lib/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion creds-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ license = "LGPL-3.0-only"
futures-lite = "2.6.0"
libwebauthn = "0.2"
serde = { version = "1", features = ["derive"] }
zbus = "5.9.0"
zvariant = "5.6.0"
2 changes: 1 addition & 1 deletion creds-lib/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use futures_lite::Stream;
use crate::model::{BackgroundEvent, Device};

/// Used for communication from trusted UI to credential service
pub trait CredentialServiceClient {
pub trait FlowController {
fn get_available_public_key_devices(
&self,
) -> impl Future<Output = Result<Vec<Device>, ()>> + Send;
Expand Down
22 changes: 12 additions & 10 deletions creds-lib/src/model.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::Display;

use serde::{Deserialize, Serialize};
use zbus::zvariant::{SerializeDict, Type};
use zvariant::{SerializeDict, Type};

pub use libwebauthn::ops::webauthn::{
Assertion, GetAssertionRequest, MakeCredentialRequest, MakeCredentialResponse,
Expand All @@ -22,8 +22,8 @@ pub enum CredentialRequest {

#[derive(Clone, Debug)]
pub enum CredentialResponse {
CreatePublicKeyCredentialResponse(MakeCredentialResponseInternal),
GetPublicKeyCredentialResponse(GetAssertionResponseInternal),
CreatePublicKeyCredentialResponse(Box<MakeCredentialResponseInternal>),
GetPublicKeyCredentialResponse(Box<GetAssertionResponseInternal>),
}

impl CredentialResponse {
Expand All @@ -32,17 +32,18 @@ impl CredentialResponse {
transports: &[&str],
modality: &str,
) -> CredentialResponse {
CredentialResponse::CreatePublicKeyCredentialResponse(MakeCredentialResponseInternal::new(
response.clone(),
transports.iter().map(|s| s.to_string()).collect(),
modality.to_string(),
CredentialResponse::CreatePublicKeyCredentialResponse(Box::new(
MakeCredentialResponseInternal::new(
response.clone(),
transports.iter().map(|s| s.to_string()).collect(),
modality.to_string(),
),
))
}

pub fn from_get_assertion(assertion: &Assertion, modality: &str) -> CredentialResponse {
CredentialResponse::GetPublicKeyCredentialResponse(GetAssertionResponseInternal::new(
assertion.clone(),
modality.to_string(),
CredentialResponse::GetPublicKeyCredentialResponse(Box::new(
GetAssertionResponseInternal::new(assertion.clone(), modality.to_string()),
))
}
}
Expand Down Expand Up @@ -260,6 +261,7 @@ pub enum UsbState {
Failed(Error),
}

#[derive(Debug)]
pub enum BackgroundEvent {
UsbStateChanged(UsbState),
HybridQrStateChanged(HybridState),
Expand Down
101 changes: 4 additions & 97 deletions creds-lib/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Types for serializing across D-Bus instances

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use zbus::zvariant::{self, DeserializeDict, LE, Optional, OwnedValue, SerializeDict, Type, Value};
use zvariant::{self, DeserializeDict, LE, Optional, OwnedValue, SerializeDict, Type, Value};

use crate::model::{Operation, ViewUpdate};
use crate::model::Operation;

#[derive(Clone, Debug, Serialize, Deserialize, Type)]
pub enum BackgroundEvent {
Expand Down Expand Up @@ -96,89 +98,6 @@ impl From<CreatePublicKeyCredentialResponse> for CreateCredentialResponse {
}
}

/// Updates to send to the client
#[derive(Serialize, Deserialize, Type)]
pub enum ClientUpdate {
SetTitle(OwnedValue),
SetDevices(OwnedValue),
SetCredentials(OwnedValue),

WaitingForDevice(OwnedValue),
SelectingDevice(OwnedValue),

UsbNeedsPin(OwnedValue),
UsbNeedsUserVerification(OwnedValue),
UsbNeedsUserPresence(OwnedValue),

HybridNeedsQrCode(OwnedValue),
HybridConnecting(OwnedValue),
HybridConnected(OwnedValue),

Completed(OwnedValue),
Failed(OwnedValue),
}

impl TryFrom<ClientUpdate> for ViewUpdate {
type Error = zvariant::Error;
fn try_from(value: ClientUpdate) -> std::result::Result<ViewUpdate, Self::Error> {
match value {
ClientUpdate::SetTitle(v) => v.try_into().map(Self::SetTitle),
ClientUpdate::SetDevices(v) => {
let dbus_devices: Vec<Device> = Value::<'_>::from(v).try_into()?;
let devices: std::result::Result<Vec<crate::model::Device>, zvariant::Error> =
dbus_devices
.into_iter()
.map(|d| {
d.try_into().map_err(|_| {
zvariant::Error::Message(
"Could not deserialize devices".to_string(),
)
})
})
.collect();
Ok(Self::SetDevices(devices?))
}
ClientUpdate::SetCredentials(v) => {
let dbus_credentials: Vec<Credential> = Value::<'_>::from(v).try_into()?;
let credentials: std::result::Result<
Vec<crate::model::Credential>,
zvariant::Error,
> = dbus_credentials
.into_iter()
.map(|creds| Ok(creds.into()))
.collect();
Ok(Self::SetCredentials(credentials?))
}

ClientUpdate::WaitingForDevice(v) => {
let dbus_device: Device = Value::<'_>::from(v).try_into()?;
let device: crate::model::Device = dbus_device.try_into().map_err(|_| {
zvariant::Error::Message("Could not deserialize device".to_string())
})?;
Ok(Self::WaitingForDevice(device))
}
ClientUpdate::SelectingDevice(_) => Ok(Self::SelectingDevice),

ClientUpdate::UsbNeedsPin(v) => v.try_into().map(|x: i32| {
let attempts_left = if x == -1 { None } else { Some(x as u32) };
Self::UsbNeedsPin { attempts_left }
}),
ClientUpdate::UsbNeedsUserVerification(v) => v.try_into().map(|x: i32| {
let attempts_left = if x == -1 { None } else { Some(x as u32) };
Self::UsbNeedsUserVerification { attempts_left }
}),
ClientUpdate::UsbNeedsUserPresence(_) => Ok(Self::UsbNeedsUserPresence),

ClientUpdate::HybridNeedsQrCode(v) => v.try_into().map(Self::HybridNeedsQrCode),
ClientUpdate::HybridConnecting(_) => Ok(Self::HybridConnecting),
ClientUpdate::HybridConnected(_) => Ok(Self::HybridConnected),

ClientUpdate::Completed(_) => Ok(Self::Completed),
ClientUpdate::Failed(v) => v.try_into().map(Self::Failed),
}
}
}

#[derive(SerializeDict, DeserializeDict, Type, Value)]
#[zvariant(signature = "dict")]
pub struct Credential {
Expand Down Expand Up @@ -207,18 +126,6 @@ impl From<crate::model::Credential> for Credential {
}
}

/*
impl TryFrom<Value<'_>> for Credential {
type Error = zvariant::Error;
fn try_from(value: Value<'_>) -> std::result::Result<Self, Self::Error> {
let ctx = zvariant::serialized::Context::new_dbus(LE, 0);
let encoded = zvariant::to_bytes(ctx, &value)?;
let credential: Credential = encoded.deserialize()?.0;
Ok(credential)
}
}
*/

#[derive(SerializeDict, DeserializeDict, Type)]
#[zvariant(signature = "a{sv}")]
pub struct Device {
Expand Down
2 changes: 1 addition & 1 deletion creds-ui/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions creds-ui/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_std::stream::Stream;
use creds_lib::client::CredentialServiceClient;
use creds_lib::client::FlowController;
use futures_lite::StreamExt;
use zbus::{Connection, zvariant};

Expand All @@ -20,7 +20,7 @@ impl DbusCredentialClient {
}
}

impl CredentialServiceClient for DbusCredentialClient {
impl FlowController for DbusCredentialClient {
async fn get_available_public_key_devices(
&self,
) -> std::result::Result<Vec<creds_lib::model::Device>, ()> {
Expand Down Expand Up @@ -81,7 +81,7 @@ impl CredentialServiceClient for DbusCredentialClient {
.initiate_event_stream()
.await
.map_err(|err| tracing::error!("Failed to initialize event stream: {err}"))
.and_then(|_| Ok(stream))
.map(|_| stream)
}

async fn enter_client_pin(&mut self, pin: String) -> std::result::Result<(), ()> {
Expand Down
2 changes: 0 additions & 2 deletions creds-ui/src/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ pub trait FlowControlService {

pub struct UiControlService {
pub request_tx: Sender<crate::dbus::ViewRequest>,
// pub update_tx: Sender<BackgroundEvent>,
}

/// These methods are called by the credential service to control the UI.
Expand All @@ -40,5 +39,4 @@ impl UiControlService {
.await
.map_err(|_| fdo::Error::Failed("UI failed to launch".to_string()))
}
// fn send_state_changed(&self, event: BackgroundEvent) {}
}
54 changes: 8 additions & 46 deletions creds-ui/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,32 @@ use std::{sync::Arc, thread::JoinHandle};
use async_std::{channel::Receiver, sync::Mutex as AsyncMutex};

use creds_lib::server::ViewRequest;
use creds_lib::{
client::CredentialServiceClient,
model::{Operation, ViewUpdate},
};
use creds_lib::{client::FlowController, model::ViewUpdate};

use view_model::ViewEvent;

pub(super) fn start_gui_thread<C: CredentialServiceClient + Send + Sync + 'static>(
pub(super) fn start_gui_thread<F: FlowController + Send + Sync + 'static>(
rx: Receiver<ViewRequest>,
client: C,
flow_controller: F,
) -> Result<JoinHandle<()>, std::io::Error> {
thread::Builder::new().name("gui".into()).spawn(move || {
let client = Arc::new(AsyncMutex::new(client));
let flow_controller = Arc::new(AsyncMutex::new(flow_controller));
// D-Bus received a request and needs a window open
while let Ok(view_request) = rx.recv_blocking() {
run_gui(client.clone(), view_request);
run_gui(flow_controller.clone(), view_request);
}
})
}

fn run_gui<C: CredentialServiceClient + Send + Sync + 'static>(
client: Arc<AsyncMutex<C>>,
fn run_gui<F: FlowController + Send + Sync + 'static>(
flow_controller: Arc<AsyncMutex<F>>,
request: ViewRequest,
) {
let operation = request.operation;
let (tx_update, rx_update) = async_std::channel::unbounded::<ViewUpdate>();
let (tx_event, rx_event) = async_std::channel::unbounded::<ViewEvent>();
let event_loop = async_std::task::spawn(async move {
let mut vm = view_model::ViewModel::new(operation, client, rx_event, tx_update);
let mut vm = view_model::ViewModel::new(operation, flow_controller, rx_event, tx_update);
vm.start_event_loop().await;
println!("event loop ended?");
});
Expand All @@ -43,38 +40,3 @@ fn run_gui<C: CredentialServiceClient + Send + Sync + 'static>(

async_std::task::block_on(event_loop.cancel());
}

trait GuiClient {
/// Mark the GUI as ready to receive events from credential service.
/// Returns a queue of updates for updating the GUI state.
async fn initiate_event_stream(&self) -> Result<Receiver<ViewUpdate>, ()>;

/// Select a authenticator or transport to interact with a credential.
async fn select_device(&self, device_id: String) -> Result<(), ()>;

/// Send the client PIN to an authenticator.
async fn enter_client_pin(&self, pin: String) -> Result<(), ()>;

/// Confirm user's credential selection when an authenticator returns multiple credentials.
async fn select_credential(&self, credential_id: String) -> Result<(), ()>;
}

struct InProcessGuiClient {}

impl GuiClient for InProcessGuiClient {
async fn initiate_event_stream(&self) -> Result<Receiver<ViewUpdate>, ()> {
todo!()
}

async fn select_device(&self, device_id: String) -> Result<(), ()> {
todo!()
}

async fn enter_client_pin(&self, pin: String) -> Result<(), ()> {
todo!()
}

async fn select_credential(&self, credential_id: String) -> Result<(), ()> {
todo!()
}
}
12 changes: 5 additions & 7 deletions creds-ui/src/gui/view_model/gtk/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gdk, gio, glib};

use super::{window::ExampleApplicationWindow, ViewModel};
use super::{ViewModel, window::ExampleApplicationWindow};
use crate::config::{APP_ID, PKGDATADIR, PROFILE, VERSION};
use crate::gui::view_model::{ViewEvent, ViewUpdate};

mod imp {
use super::*;
use glib::{clone, WeakRef};
use glib::{WeakRef, clone};
use std::{
cell::{OnceCell, RefCell},
time::Duration,
Expand Down Expand Up @@ -142,15 +142,13 @@ impl ExampleApplication {
fn show_about_dialog(&self) {
let dialog = gtk::AboutDialog::builder()
.logo_icon_name(APP_ID)
// Insert your license of choice here
// .license_type(gtk::License::MitX11)
.website("https://github.com/iinuwa/linux-webauthn-portal-api")
.license_type(gtk::License::Lgpl30Only)
.website("https://github.com/linux-credentials/linux-webauthn-portal-api")
.version(VERSION)
.transient_for(&self.main_window())
.translator_credits(gettext("translator-credits"))
.modal(true)
.authors(vec!["Isaiah Inuwa"])
.artists(vec!["Isaiah Inuwa"])
.authors(vec!["Isaiah Inuwa <isaiah.inuwa@gmail.com>"])
.build();

dialog.present();
Expand Down
9 changes: 6 additions & 3 deletions creds-ui/src/gui/view_model/gtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod device;
mod window;

use async_std::channel::{Receiver, Sender};
use gettextrs::{gettext, LocaleCategory};
use gettextrs::{LocaleCategory, gettext};
use glib::clone;
use gtk::gdk::Texture;
use gtk::gdk_pixbuf::Pixbuf;
Expand Down Expand Up @@ -145,8 +145,11 @@ impl ViewModel {
}
ViewUpdate::UsbNeedsUserVerification { attempts_left } => {
let prompt = match attempts_left {
Some(1) => "Touch your device again. 1 attempt remaining.".to_string(),
Some(attempts_left) => format!("Touch your device again. {attempts_left} attempts remaining."),
Some(1) => "Touch your device again. 1 attempt remaining."
.to_string(),
Some(attempts_left) => format!(
"Touch your device again. {attempts_left} attempts remaining."
),
None => "Touch your device.".to_string(),
};
view_model.set_prompt(prompt);
Expand Down
4 changes: 2 additions & 2 deletions creds-ui/src/gui/view_model/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::cell::RefCell;
use glib::Properties;
use gtk::gdk::Texture;
use gtk::subclass::prelude::*;
use gtk::{Picture, prelude::*};
use gtk::{
gio,
glib::{self, clone},
};
use gtk::{prelude::*, Picture};

use super::application::ExampleApplication;
use super::{device::DeviceObject, ViewModel};
use super::{ViewModel, device::DeviceObject};
use crate::config::{APP_ID, PROFILE};
use crate::gui::view_model::Transport;

Expand Down
Loading
Loading