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
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"args": [],
"env": {
"GSETTINGS_SCHEMA_DIR": "${workspaceFolder}/build/credentialsd-ui/data",
"RUST_LOG": "creds_ui=debug,zbus::trace,zbus::object_server::debug"
"RUST_LOG": "credentialsd_ui=debug,zbus::trace,zbus::object_server::debug"
},
"sourceLanguages": ["rust"],
"cwd": "${workspaceFolder}",
Expand Down
6 changes: 5 additions & 1 deletion credentialsd-common/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::pin::Pin;

use futures_lite::Stream;

use crate::model::{BackgroundEvent, Device};
use crate::{
model::{BackgroundEvent, Device},
server::RequestId,
};

/// Used for communication from trusted UI to credential service
pub trait FlowController {
Expand All @@ -22,4 +25,5 @@ pub trait FlowController {
&self,
credential_id: String,
) -> impl Future<Output = Result<(), ()>> + Send;
fn cancel_request(&self, request_id: RequestId) -> impl Future<Output = Result<(), ()>> + Send;
}
1 change: 1 addition & 0 deletions credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub enum ViewUpdate {
HybridConnected,

Completed,
Cancelled,
Failed(String),
}

Expand Down
14 changes: 8 additions & 6 deletions credentialsd-common/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ impl TryFrom<BackgroundEvent> for crate::model::BackgroundEvent {
}
}

impl TryFrom<crate::model::BackgroundEvent> for BackgroundEvent {
type Error = zvariant::Error;
fn try_from(value: crate::model::BackgroundEvent) -> Result<Self, Self::Error> {
let event = match value {
impl From<crate::model::BackgroundEvent> for BackgroundEvent {
fn from(value: crate::model::BackgroundEvent) -> Self {
match value {
crate::model::BackgroundEvent::HybridQrStateChanged(state) => {
let state: HybridState = state.into();
let value = Value::new(state)
Expand All @@ -52,8 +51,7 @@ impl TryFrom<crate::model::BackgroundEvent> for BackgroundEvent {

BackgroundEvent::UsbStateChanged(value)
}
};
Ok(event)
}
}
}

Expand Down Expand Up @@ -327,6 +325,9 @@ impl From<HybridState> for Value<'_> {
}
}

/// Identifier for a request to be used for cancellation.
pub type RequestId = u32;

#[derive(Serialize, Deserialize, Type)]
pub enum ServiceError {
/// Some unknown error with the authenticator occurred.
Expand Down Expand Up @@ -625,6 +626,7 @@ impl From<UsbState> for Value<'_> {
#[derive(Serialize, Deserialize, Type)]
pub struct ViewRequest {
pub operation: Operation,
pub id: RequestId,
}

fn value_to_owned(value: &Value<'_>) -> OwnedValue {
Expand Down
15 changes: 14 additions & 1 deletion credentialsd-ui/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_std::stream::Stream;
use credentialsd_common::client::FlowController;
use credentialsd_common::{client::FlowController, server::RequestId};
use futures_lite::StreamExt;
use zbus::{Connection, zvariant};

Expand Down Expand Up @@ -101,4 +101,17 @@ impl FlowController for DbusCredentialClient {
.await
.map_err(|err| tracing::error!("Failed to select credential: {err}"))
}

async fn cancel_request(&self, request_id: RequestId) -> Result<(), ()> {
if self
.proxy()
.await?
.cancel_request(request_id)
.await
.is_err()
{
tracing::warn!("Failed to cancel request {request_id}");
}
Ok(())
}
}
10 changes: 4 additions & 6 deletions credentialsd-ui/src/dbus.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_std::channel::Sender;
use credentialsd_common::server::{BackgroundEvent, Device, ViewRequest};
use credentialsd_common::server::{BackgroundEvent, Device, RequestId, ViewRequest};
use zbus::{fdo, interface, proxy};

#[proxy(
Expand All @@ -20,22 +20,20 @@ pub trait FlowControlService {
async fn select_device(&self, device_id: String) -> fdo::Result<()>;
async fn enter_client_pin(&self, pin: String) -> fdo::Result<()>;
async fn select_credential(&self, credential_id: String) -> fdo::Result<()>;
async fn cancel_request(&self, request_id: RequestId) -> fdo::Result<()>;

#[zbus(signal)]
async fn state_changed(update: BackgroundEvent) -> zbus::Result<()>;
}

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

/// These methods are called by the credential service to control the UI.
#[interface(name = "xyz.iinuwa.credentialsd.UiControl1")]
impl UiControlService {
async fn launch_ui(
&self,
request: credentialsd_common::server::ViewRequest,
) -> fdo::Result<()> {
async fn launch_ui(&self, request: ViewRequest) -> fdo::Result<()> {
tracing::debug!("Received UI launch request");
self.request_tx
.send(request)
Expand Down
11 changes: 9 additions & 2 deletions credentialsd-ui/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@ fn run_gui<F: FlowController + Send + Sync + 'static>(
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, flow_controller, rx_event, tx_update);
let mut vm =
view_model::ViewModel::new(operation, flow_controller.clone(), rx_event, tx_update);
vm.start_event_loop().await;
println!("event loop ended?");
tracing::debug!("Finishing user request.");
// If cancellation fails, that's fine.
let _ = flow_controller
.lock()
.await
.cancel_request(request.id)
.await;
});

view_model::gtk::start_gtk_app(tx_event, rx_update);
Expand Down
16 changes: 16 additions & 0 deletions credentialsd-ui/src/gui/view_model/gtk/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::config::{APP_ID, PKGDATADIR, PROFILE, VERSION};
use crate::gui::view_model::{ViewEvent, ViewUpdate};

mod imp {
use crate::gui::view_model::gtk::ModelState;

use super::*;
use glib::{WeakRef, clone};
use std::{
Expand Down Expand Up @@ -67,6 +69,20 @@ mod imp {
));
}
});
let window3 = window.clone();
// TODO: merge these state callbacks into a single function
vm2.clone().connect_state_notify(move |vm| {
if let ModelState::Cancelled = vm.state() {
glib::spawn_future_local(clone!(
#[weak]
window3,
async move {
gtk::prelude::WidgetExt::activate_action(&window3, "window.close", None)
.unwrap()
}
));
}
});
self.window
.set(window.downgrade())
.expect("Window already set.");
Expand Down
17 changes: 17 additions & 0 deletions credentialsd-ui/src/gui/view_model/gtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ mod imp {
#[property(get, set)]
pub prompt: RefCell<String>,

#[property(get, set, builder(ModelState::Pending))]
pub state: RefCell<ModelState>,

#[property(get, set)]
pub completed: RefCell<bool>,

Expand Down Expand Up @@ -189,10 +192,14 @@ impl ViewModel {
view_model.set_failed(true);
view_model.set_prompt(error_msg);
}
ViewUpdate::Cancelled => {
view_model.set_state(ModelState::Cancelled)
}
}
}
Err(e) => {
debug!("ViewModel event listener interrupted: {}", e);
view_model.set_state(ModelState::Cancelled);
break;
}
}
Expand Down Expand Up @@ -361,3 +368,13 @@ pub fn start_gtk_app(
let app = ExampleApplication::new(tx_event, rx_update);
app.run();
}

#[derive(Clone, Copy, Debug, Default, glib::Enum)]
#[enum_type(name = "ModelState")]
pub enum ModelState {
#[default]
Pending,
Completed,
Failed,
Cancelled,
}
13 changes: 13 additions & 0 deletions credentialsd-ui/src/gui/view_model/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use crate::gui::view_model::Transport;
mod imp {
use gtk::Picture;

use crate::gui::view_model::ViewEvent;

use super::*;

#[derive(Debug, Properties, gtk::CompositeTemplate)]
Expand Down Expand Up @@ -106,6 +108,17 @@ mod imp {
impl WindowImpl for ExampleApplicationWindow {
// Save window state on delete event
fn close_request(&self) -> glib::Propagation {
if let Some(vm) = self.view_model.borrow().as_ref() {
if vm
.get_sender()
.send_blocking(ViewEvent::UserCancelled)
.is_err()
{
tracing::warn!(
"Failed to notify the backend service that the user cancelled the request."
);
};
}
if let Err(err) = self.obj().save_window_size() {
tracing::warn!("Failed to save window state, {}", &err);
}
Expand Down
11 changes: 10 additions & 1 deletion credentialsd-ui/src/gui/view_model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ impl<F: FlowController + Send> ViewModel<F> {
.unwrap();
}
}
Event::View(ViewEvent::UserCancelled) => {
break;
}

Event::Background(BackgroundEvent::UsbStateChanged(state)) => {
match state {
Expand Down Expand Up @@ -269,13 +272,18 @@ impl<F: FlowController + Send> ViewModel<F> {
}
HybridState::UserCancelled => {
self.hybrid_qr_code_data = None;
break;
}
HybridState::Failed => {
self.hybrid_qr_code_data = None;
self.tx_update.send(ViewUpdate::Failed(String::from("Something went wrong. Try again later or use a different authenticator."))).await.unwrap();
}
};
}
} /*
Event::Background(BackgroundEvent::RequestCancelled(request_id)) => {
break;
}
*/
};
}
}
Expand All @@ -287,6 +295,7 @@ pub enum ViewEvent {
DeviceSelected(String),
CredentialSelected(String),
UsbPinEntered(String),
UserCancelled,
}

pub enum Event {
Expand Down
7 changes: 4 additions & 3 deletions credentialsd/Cargo.lock

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

1 change: 1 addition & 0 deletions credentialsd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ credentialsd-common = { path = "../credentialsd-common" }
futures-lite = "2.6.0"
libwebauthn = "~0.2.2"
openssl = "0.10.72"
rand = "0.9.2"
ring = "0.17.14"
rustls = { version = "0.23.27", default-features = false, features = ["std", "tls12", "ring", "log", "logging", "prefer-post-quantum"] }
serde = { version = "1.0.219", features = ["derive"] }
Expand Down
Loading