Skip to content

Commit b98d7e3

Browse files
authored
Merge pull request #151 from linux-credentials/portal-backend-api
Portal backend api
2 parents 7b38479 + 65cf12d commit b98d7e3

19 files changed

Lines changed: 1140 additions & 307 deletions

File tree

credentialsd-common/src/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::pin::Pin;
33
use futures_lite::Stream;
44

55
use crate::{
6-
model::Device,
7-
server::{BackgroundEvent, RequestId},
6+
model::{Device, RequestId},
7+
server::BackgroundEvent,
88
};
99

1010
/// Used for communication from trusted UI to credential service

credentialsd-common/src/model.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,17 @@ pub struct Device {
4040

4141
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
4242
pub enum Operation {
43-
Create,
44-
Get,
43+
PublicKeyCreate,
44+
PublicKeyGet,
45+
}
46+
47+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
48+
pub struct PortalBackendOptions {
49+
/// Top-level origin of the request if different from the origin.
50+
pub top_origin: Optional<String>,
51+
52+
/// RP ID of the request. Required for WebAuthn/PublicKey requests.
53+
pub rp_id: Optional<String>,
4554
}
4655

4756
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
@@ -122,6 +131,9 @@ pub struct RequestingParty {
122131
pub origin: String,
123132
}
124133

134+
/// Identifier for a request to be used for cancellation.
135+
pub type RequestId = u32;
136+
125137
// TODO: Move to credentialsd-ui
126138
#[derive(Debug, Clone, Serialize, Deserialize)]
127139
pub enum ViewUpdate {
@@ -254,6 +266,41 @@ pub enum NfcState {
254266
Failed(Error),
255267
}
256268

269+
pub enum BackendRequest {
270+
/// Start Hybrid discovery
271+
StartHybridDiscovery,
272+
273+
/// Start NFC discovery
274+
StartNfcDiscovery,
275+
276+
/// Start USB discovery
277+
StartUsbDiscovery,
278+
279+
/// Send client PIN
280+
EnterClientPin(String),
281+
282+
/// Select a credential by credential ID
283+
SelectCredential(String),
284+
285+
CancelRequest,
286+
}
287+
288+
impl std::fmt::Debug for BackendRequest {
289+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290+
match self {
291+
Self::StartHybridDiscovery => write!(f, "StartHybridDiscovery"),
292+
Self::StartNfcDiscovery => write!(f, "StartNfcDiscovery"),
293+
Self::StartUsbDiscovery => write!(f, "StartUsbDiscovery"),
294+
Self::EnterClientPin(_) => f
295+
.debug_tuple("EnterClientPin")
296+
.field(&"******".to_string())
297+
.finish(),
298+
Self::SelectCredential(arg0) => f.debug_tuple("SelectCredential").field(arg0).finish(),
299+
Self::CancelRequest => write!(f, "CancelRequest"),
300+
}
301+
}
302+
}
303+
257304
#[derive(Debug, Clone)]
258305
pub enum Error {
259306
/// Some unknown error with the authenticator occurred.

credentialsd-common/src/server.rs

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use serde::{
88
};
99
use zvariant::{
1010
self, Array, DeserializeDict, DynamicDeserialize, NoneValue, Optional, OwnedValue,
11-
SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields,
11+
SerializeDict, Signature, Str, Structure, StructureBuilder, Type, Value, signature::Fields,
1212
};
1313

14-
use crate::model::{Device, Operation, RequestingApplication};
14+
use crate::model::{BackendRequest, Device, Operation, RequestId, RequestingApplication};
1515

1616
const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static {
1717
fields: &[&Signature::U32, &Signature::Variant],
@@ -49,6 +49,13 @@ const BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED: u32 = 0x80000006;
4949
const BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED: u32 = 0x80000007;
5050
const BACKGROUND_EVENT_ERROR_PIN_NOT_SET: u32 = 0x80000008;
5151

52+
const BACKEND_REQUEST_START_HYBRID_DISCOVERY: u32 = 0x01;
53+
const BACKEND_REQUEST_START_USB_DISCOVERY: u32 = 0x02;
54+
const BACKEND_REQUEST_START_NFC_DISCOVERY: u32 = 0x03;
55+
const BACKEND_REQUEST_ENTER_CLIENT_PIN: u32 = 0x04;
56+
const BACKEND_REQUEST_SELECT_CREDENTIAL: u32 = 0x05;
57+
const BACKEND_REQUEST_CANCEL_REQUEST: u32 = 0x06;
58+
5259
/// Flattened enum BackgroundEvent for sending across D-Bus.
5360
#[derive(Debug, Clone, PartialEq)]
5461
pub enum BackgroundEvent {
@@ -267,6 +274,94 @@ impl<'de> Deserialize<'de> for BackgroundEvent {
267274
}
268275
}
269276

277+
impl Type for BackendRequest {
278+
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
279+
}
280+
281+
impl From<&BackendRequest> for Structure<'_> {
282+
fn from(value: &BackendRequest) -> Self {
283+
match value {
284+
BackendRequest::StartHybridDiscovery => tag_value_to_struct(0x01, None),
285+
BackendRequest::StartNfcDiscovery => tag_value_to_struct(0x02, None),
286+
BackendRequest::StartUsbDiscovery => tag_value_to_struct(0x03, None),
287+
BackendRequest::EnterClientPin(pin) => {
288+
tag_value_to_struct(0x04, Some(Value::Str(pin.into())))
289+
}
290+
BackendRequest::SelectCredential(credential_id) => {
291+
tag_value_to_struct(0x05, Some(Value::Str(credential_id.into())))
292+
}
293+
BackendRequest::CancelRequest => tag_value_to_struct(0x06, None),
294+
}
295+
}
296+
}
297+
298+
impl TryFrom<&Structure<'_>> for BackendRequest {
299+
type Error = zvariant::Error;
300+
301+
fn try_from(value: &Structure<'_>) -> Result<Self, Self::Error> {
302+
let (tag, value) = parse_tag_value_struct(value)?;
303+
304+
match tag {
305+
0x01 => Ok(BackendRequest::StartHybridDiscovery),
306+
0x02 => Ok(BackendRequest::StartNfcDiscovery),
307+
0x03 => Ok(BackendRequest::StartUsbDiscovery),
308+
0x04 => {
309+
let s: Str = value.downcast_ref()?;
310+
if s.is_empty() {
311+
return Err(zvariant::Error::invalid_length(
312+
s.len(),
313+
&"a non-empty string",
314+
));
315+
}
316+
Ok(BackendRequest::EnterClientPin(s.as_str().to_string()))
317+
}
318+
0x05 => {
319+
let s: Str = value.downcast_ref()?;
320+
if s.is_empty() {
321+
return Err(zvariant::Error::invalid_length(
322+
s.len(),
323+
&"a non-empty string",
324+
));
325+
}
326+
Ok(BackendRequest::SelectCredential(s.as_str().to_string()))
327+
}
328+
0x06 => Ok(BackendRequest::CancelRequest),
329+
_ => Err(zvariant::Error::Message(format!(
330+
"Unknown BackendRequest tag : {tag}"
331+
))),
332+
}
333+
}
334+
}
335+
336+
impl Serialize for BackendRequest {
337+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
338+
where
339+
S: serde::Serializer,
340+
{
341+
let structure: Structure = self.into();
342+
structure.serialize(serializer)
343+
}
344+
}
345+
346+
impl<'de> Deserialize<'de> for BackendRequest {
347+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
348+
where
349+
D: serde::Deserializer<'de>,
350+
{
351+
let d = Structure::deserializer_for_signature(TAG_VALUE_SIGNATURE).map_err(|err| {
352+
D::Error::custom(format!(
353+
"could not create deserializer for tag-value struct: {err}"
354+
))
355+
})?;
356+
let structure = d.deserialize(deserializer)?;
357+
(&structure).try_into().map_err(|err| {
358+
D::Error::custom(format!(
359+
"could not deserialize structure into BackendRequest: {err}"
360+
))
361+
})
362+
}
363+
}
364+
270365
#[derive(Clone, Debug, DeserializeDict, Type)]
271366
#[zvariant(signature = "dict")]
272367
pub struct CreateCredentialRequest {
@@ -412,10 +507,7 @@ impl From<GetPublicKeyCredentialResponse> for GetCredentialResponse {
412507
}
413508
}
414509

415-
/// Identifier for a request to be used for cancellation.
416-
pub type RequestId = u32;
417-
418-
#[derive(Serialize, Deserialize, Type)]
510+
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
419511
pub struct ViewRequest {
420512
pub operation: Operation,
421513

@@ -435,7 +527,7 @@ pub struct ViewRequest {
435527
pub window_handle: Optional<WindowHandle>,
436528
}
437529

438-
#[derive(Type, PartialEq, Debug)]
530+
#[derive(Clone, Debug, PartialEq, Type)]
439531
#[zvariant(signature = "s")]
440532
pub enum WindowHandle {
441533
Wayland(String),

credentialsd-ui/src/client.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
use async_std::stream::Stream;
2-
use credentialsd_common::{client::FlowController, server::RequestId};
1+
use async_std::{
2+
channel::{Receiver, Sender},
3+
stream::Stream,
4+
sync::Mutex as AsyncMutex,
5+
};
6+
use credentialsd_common::{
7+
client::FlowController,
8+
model::{BackendRequest, RequestId},
9+
server::BackgroundEvent,
10+
};
311
use futures_lite::StreamExt;
412
use zbus::Connection;
513

@@ -118,3 +126,51 @@ impl FlowController for DbusCredentialClient {
118126
Ok(())
119127
}
120128
}
129+
130+
#[derive(Debug)]
131+
pub struct FlowControlClient {
132+
pub tx: Sender<BackendRequest>,
133+
pub rx: AsyncMutex<Option<Receiver<BackgroundEvent>>>,
134+
}
135+
136+
impl FlowControlClient {
137+
pub async fn discover_hybrid_authenticators(&self) -> Result<(), ()> {
138+
self.send(BackendRequest::StartHybridDiscovery).await
139+
}
140+
141+
pub async fn discover_nfc_authenticators(&mut self) -> Result<(), ()> {
142+
self.send(BackendRequest::StartNfcDiscovery).await
143+
}
144+
145+
pub async fn discover_usb_authenticators(&mut self) -> Result<(), ()> {
146+
self.send(BackendRequest::StartUsbDiscovery).await
147+
}
148+
149+
pub async fn enter_client_pin(&mut self, pin: String) -> Result<(), ()> {
150+
self.send(BackendRequest::EnterClientPin(pin)).await
151+
}
152+
153+
pub async fn select_credential(&self, credential_id: String) -> Result<(), ()> {
154+
self.send(BackendRequest::SelectCredential(credential_id))
155+
.await
156+
}
157+
158+
pub async fn cancel_request(&self) -> Result<(), ()> {
159+
self.send(BackendRequest::CancelRequest).await
160+
}
161+
162+
/// Returns a channel for background events.
163+
/// Can only be called once; returns an error if the subscription has already been taken.
164+
pub async fn subscribe(&mut self) -> Result<Receiver<BackgroundEvent>, ()> {
165+
self.rx.lock().await.take().ok_or_else(|| {
166+
tracing::error!("Subscribe has already been called.");
167+
})
168+
}
169+
170+
async fn send(&self, request: BackendRequest) -> Result<(), ()> {
171+
match self.tx.send(request).await {
172+
Ok(_) => Ok(()),
173+
Err(_) => Err(()),
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)