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
117 changes: 117 additions & 0 deletions src/irmaclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ pub struct SessionData {
pub session_ptr: Qr,
/// The token for further interaction with the session
pub token: SessionToken,
/// Information needed to drive the IRMA/Yivi frontend directly (e.g. for
/// pairing). Present since irmago v0.14.0; `None` when the server does not
/// return a `frontendRequest` block.
#[serde(
rename = "frontendRequest",
default,
skip_serializing_if = "Option::is_none"
)]
pub frontend_request: Option<FrontendRequest>,
}

/// The `frontendRequest` block returned by irmago on session start, used to
/// communicate with the IRMA/Yivi frontend directly.
///
/// Since pairing is mandatory by default for IRMA clients (irmago v0.13.0),
/// the [`authorization`](Self::authorization) token is required to complete the
/// pairing handshake.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct FrontendRequest {
/// Authorization token used to authenticate to the frontend endpoints.
pub authorization: String,
/// The lowest frontend protocol version the server supports, if reported.
#[serde(
rename = "minProtocolVersion",
default,
skip_serializing_if = "Option::is_none"
)]
pub min_protocol_version: Option<String>,
/// The highest frontend protocol version the server supports, if reported.
#[serde(
rename = "maxProtocolVersion",
default,
skip_serializing_if = "Option::is_none"
)]
pub max_protocol_version: Option<String>,
}

/// Token used to identify individual sessions on the server
Expand Down Expand Up @@ -188,3 +224,84 @@ impl IrmaClientBuilder {
}
}
}

#[cfg(test)]
mod tests {
use crate::{FrontendRequest, SessionData};

#[test]
fn test_decode_session_data_with_frontend_request() {
let data = serde_json::from_str::<SessionData>(
r#"
{
"token": "KzxuWKwL5KGLKr4uerws",
"sessionPtr": {
"u": "https://example.com/irma/session/abc",
"irmaqr": "disclosing"
},
"frontendRequest": {
"authorization": "O5Ld2vAr9pkz7ELzWqgM",
"minProtocolVersion": "1.0",
"maxProtocolVersion": "1.1"
}
}
"#,
)
.unwrap();

assert_eq!(
data.frontend_request,
Some(FrontendRequest {
authorization: "O5Ld2vAr9pkz7ELzWqgM".into(),
min_protocol_version: Some("1.0".into()),
max_protocol_version: Some("1.1".into()),
})
);

// Round-trips back to JSON without losing the frontend request.
let reparsed =
serde_json::from_str::<SessionData>(&serde_json::to_string(&data).unwrap()).unwrap();
assert_eq!(reparsed.frontend_request, data.frontend_request);
}

#[test]
fn test_decode_session_data_without_frontend_request() {
// Servers older than irmago v0.14.0 omit the frontendRequest block.
let data = serde_json::from_str::<SessionData>(
r#"
{
"token": "KzxuWKwL5KGLKr4uerws",
"sessionPtr": {
"u": "https://example.com/irma/session/abc",
"irmaqr": "disclosing"
}
}
"#,
)
.unwrap();

assert_eq!(data.frontend_request, None);

// The field is skipped on serialization when absent.
let json = serde_json::to_string(&data).unwrap();
assert!(!json.contains("frontendRequest"));
}

#[test]
fn test_decode_frontend_request_without_protocol_versions() {
// Only authorization is guaranteed to be useful; versions are optional.
let request = serde_json::from_str::<FrontendRequest>(
r#"{ "authorization": "O5Ld2vAr9pkz7ELzWqgM" }"#,
)
.unwrap();

assert_eq!(
request,
FrontendRequest {
authorization: "O5Ld2vAr9pkz7ELzWqgM".into(),
min_protocol_version: None,
max_protocol_version: None,
}
);
}
}
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod sessionresult;
mod util;

pub use error::Error;
pub use irmaclient::{IrmaClient, IrmaClientBuilder, Qr, SessionData, SessionToken};
pub use irmaclient::{
FrontendRequest, IrmaClient, IrmaClientBuilder, Qr, SessionData, SessionToken,
};
pub use sessionrequest::{
AttributeRequest, ConDisCon, Credential, CredentialBuilder, DisclosureRequestBuilder,
ExtendedIrmaRequest, IrmaRequest, IssuanceRequestBuilder, SignatureRequestBuilder,
Expand Down
11 changes: 11 additions & 0 deletions tests/test_full_client_interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ fn test_full_client_interaction() {
.await
.expect("Failed to start session");

// Since irmago v0.14.0 the server returns a frontendRequest block
// with an authorization token; make sure we surface it.
let frontend_request = session
.frontend_request
.as_ref()
.expect("Expected a frontend_request on session start");
assert!(
!frontend_request.authorization.is_empty(),
"frontend_request.authorization should not be empty"
);

let status = client
.status(&session.token)
.await
Expand Down
Loading