Page MenuHomePhabricator

D11896.diff
No OneTemporary

D11896.diff

diff --git a/services/identity/src/device_list.rs b/services/identity/src/device_list.rs
--- a/services/identity/src/device_list.rs
+++ b/services/identity/src/device_list.rs
@@ -1,10 +1,118 @@
use chrono::{DateTime, Duration, Utc};
use std::collections::HashSet;
+use tracing::{error, warn};
use crate::{
- constants::DEVICE_LIST_TIMESTAMP_VALID_FOR, error::DeviceListError,
+ constants::{error_types, DEVICE_LIST_TIMESTAMP_VALID_FOR},
+ database::{DeviceListRow, DeviceListUpdate},
+ ddb_utils::DateTimeExt,
+ error::DeviceListError,
+ grpc_services::protos::auth::UpdateDeviceListRequest,
};
+// raw device list that can be serialized to JSON (and then signed in the future)
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct RawDeviceList {
+ devices: Vec<String>,
+ timestamp: i64,
+}
+
+impl From<DeviceListRow> for RawDeviceList {
+ fn from(row: DeviceListRow) -> Self {
+ Self {
+ devices: row.device_ids,
+ timestamp: row.timestamp.timestamp_millis(),
+ }
+ }
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SignedDeviceList {
+ /// JSON-stringified [`RawDeviceList`]
+ raw_device_list: String,
+ /// Current primary device signature.
+ /// NOTE: Present only when the payload is received from primary device.
+ /// It's `None` for Identity-generated device-lists
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ cur_primary_signature: Option<String>,
+ /// Previous primary device signature. Present only
+ /// if primary device has changed since last update.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ last_primary_signature: Option<String>,
+}
+
+impl SignedDeviceList {
+ /// Serialize (and sign in the future) a [`RawDeviceList`]
+ pub fn try_from_raw(raw: RawDeviceList) -> Result<Self, tonic::Status> {
+ let stringified_list = serde_json::to_string(&raw).map_err(|err| {
+ error!(
+ errorType = error_types::GRPC_SERVICES_LOG,
+ "Failed to serialize raw device list: {}", err
+ );
+ tonic::Status::failed_precondition("unexpected error")
+ })?;
+
+ Ok(Self {
+ raw_device_list: stringified_list,
+ cur_primary_signature: None,
+ last_primary_signature: None,
+ })
+ }
+
+ fn as_raw(&self) -> Result<RawDeviceList, tonic::Status> {
+ // The device list payload is sent as an escaped JSON payload.
+ // Escaped double quotes need to be trimmed before attempting to deserialize
+ serde_json::from_str(&self.raw_device_list.replace(r#"\""#, r#"""#))
+ .map_err(|err| {
+ warn!("Failed to deserialize raw device list: {}", err);
+ tonic::Status::invalid_argument("invalid device list payload")
+ })
+ }
+
+ /// Serializes the signed device list to a JSON string
+ pub fn as_json_string(&self) -> Result<String, tonic::Status> {
+ serde_json::to_string(self).map_err(|err| {
+ error!(
+ errorType = error_types::GRPC_SERVICES_LOG,
+ "Failed to serialize device list updates: {}", err
+ );
+ tonic::Status::failed_precondition("unexpected error")
+ })
+ }
+}
+
+impl TryFrom<UpdateDeviceListRequest> for SignedDeviceList {
+ type Error = tonic::Status;
+ fn try_from(request: UpdateDeviceListRequest) -> Result<Self, Self::Error> {
+ serde_json::from_str(&request.new_device_list).map_err(|err| {
+ warn!("Failed to deserialize device list update: {}", err);
+ tonic::Status::invalid_argument("invalid device list payload")
+ })
+ }
+}
+
+impl TryFrom<SignedDeviceList> for DeviceListUpdate {
+ type Error = tonic::Status;
+ fn try_from(signed_list: SignedDeviceList) -> Result<Self, Self::Error> {
+ let RawDeviceList {
+ devices,
+ timestamp: raw_timestamp,
+ } = signed_list.as_raw()?;
+ let timestamp = DateTime::<Utc>::from_utc_timestamp_millis(raw_timestamp)
+ .ok_or_else(|| {
+ error!(
+ errorType = error_types::GRPC_SERVICES_LOG,
+ "Failed to parse RawDeviceList timestamp!"
+ );
+ tonic::Status::invalid_argument("invalid timestamp")
+ })?;
+ Ok(DeviceListUpdate::new(devices, timestamp))
+ }
+}
+
/// Returns `true` if given timestamp is valid. The timestamp is considered
/// valid under the following condition:
/// - `new_timestamp` is greater than `previous_timestamp` (if provided)
@@ -191,6 +299,114 @@
mod tests {
use super::*;
+ #[test]
+ fn deserialize_device_list_signature() {
+ let payload_with_signature = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}","curPrimarySignature":"foo"}"#;
+ let payload_without_signatures = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":222222222}"}"#;
+
+ let list_with_signature: SignedDeviceList =
+ serde_json::from_str(payload_with_signature).unwrap();
+ let list_without_signatures: SignedDeviceList =
+ serde_json::from_str(payload_without_signatures).unwrap();
+
+ assert_eq!(
+ list_with_signature.cur_primary_signature,
+ Some("foo".to_string())
+ );
+ assert!(list_with_signature.last_primary_signature.is_none());
+
+ assert!(list_without_signatures.cur_primary_signature.is_none());
+ assert!(list_without_signatures.last_primary_signature.is_none());
+ }
+
+ #[test]
+ fn serialize_device_list_signatures() {
+ let raw_list = r#"{"devices":["device1"],"timestamp":111111111}"#;
+
+ let expected_payload_without_signatures = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}"}"#;
+ let device_list_without_signature = SignedDeviceList {
+ raw_device_list: raw_list.to_string(),
+ cur_primary_signature: None,
+ last_primary_signature: None,
+ };
+ assert_eq!(
+ device_list_without_signature.as_json_string().unwrap(),
+ expected_payload_without_signatures
+ );
+
+ let expected_payload_with_signature = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}","curPrimarySignature":"foo"}"#;
+ let device_list_with_cur_signature = SignedDeviceList {
+ raw_device_list: raw_list.to_string(),
+ cur_primary_signature: Some("foo".to_string()),
+ last_primary_signature: None,
+ };
+ assert_eq!(
+ device_list_with_cur_signature.as_json_string().unwrap(),
+ expected_payload_with_signature
+ );
+ }
+
+ #[test]
+ fn serialize_device_list_updates() {
+ let raw_updates = vec![
+ RawDeviceList {
+ devices: vec!["device1".into()],
+ timestamp: 111111111,
+ },
+ RawDeviceList {
+ devices: vec!["device1".into(), "device2".into()],
+ timestamp: 222222222,
+ },
+ ];
+
+ let expected_raw_list1 = r#"{"devices":["device1"],"timestamp":111111111}"#;
+ let expected_raw_list2 =
+ r#"{"devices":["device1","device2"],"timestamp":222222222}"#;
+
+ let signed_updates = raw_updates
+ .into_iter()
+ .map(SignedDeviceList::try_from_raw)
+ .collect::<Result<Vec<_>, _>>()
+ .expect("signing device list updates failed");
+
+ assert_eq!(signed_updates[0].raw_device_list, expected_raw_list1);
+ assert_eq!(signed_updates[1].raw_device_list, expected_raw_list2);
+
+ let stringified_updates = signed_updates
+ .iter()
+ .map(serde_json::to_string)
+ .collect::<Result<Vec<_>, _>>()
+ .expect("serialize signed device lists failed");
+
+ let expected_stringified_list1 = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}"}"#;
+ let expected_stringified_list2 = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":222222222}"}"#;
+
+ assert_eq!(stringified_updates[0], expected_stringified_list1);
+ assert_eq!(stringified_updates[1], expected_stringified_list2);
+ }
+
+ #[test]
+ fn deserialize_device_list_update() {
+ let raw_payload = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":123456789}"}"#;
+ let request = UpdateDeviceListRequest {
+ new_device_list: raw_payload.to_string(),
+ };
+
+ let signed_list = SignedDeviceList::try_from(request)
+ .expect("Failed to parse SignedDeviceList");
+ let update = DeviceListUpdate::try_from(signed_list)
+ .expect("Failed to parse DeviceListUpdate from signed list");
+
+ let expected_timestamp =
+ DateTime::<Utc>::from_utc_timestamp_millis(123456789).unwrap();
+
+ assert_eq!(update.timestamp, expected_timestamp);
+ assert_eq!(
+ update.devices,
+ vec!["device1".to_string(), "device2".to_string()]
+ );
+ }
+
#[test]
fn test_timestamp_validation() {
let valid_timestamp = Utc::now() - Duration::milliseconds(100);
diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs
--- a/services/identity/src/grpc_services/authenticated.rs
+++ b/services/identity/src/grpc_services/authenticated.rs
@@ -1,7 +1,8 @@
use std::collections::HashMap;
use crate::config::CONFIG;
-use crate::database::{DeviceListRow, DeviceListUpdate};
+use crate::database::DeviceListUpdate;
+use crate::device_list::{RawDeviceList, SignedDeviceList};
use crate::{
client_service::{handle_db_error, UpdateState, WorkflowInProgress},
constants::{error_types, request_metadata},
@@ -633,109 +634,6 @@
}
}
-// raw device list that can be serialized to JSON (and then signed in the future)
-#[derive(serde::Serialize, serde::Deserialize)]
-struct RawDeviceList {
- devices: Vec<String>,
- timestamp: i64,
-}
-
-impl From<DeviceListRow> for RawDeviceList {
- fn from(row: DeviceListRow) -> Self {
- Self {
- devices: row.device_ids,
- timestamp: row.timestamp.timestamp_millis(),
- }
- }
-}
-
-#[derive(serde::Serialize, serde::Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct SignedDeviceList {
- /// JSON-stringified [`RawDeviceList`]
- raw_device_list: String,
- /// Current primary device signature.
- /// NOTE: Present only when the payload is received from primary device.
- /// It's `None` for Identity-generated device-lists
- #[serde(default)]
- #[serde(skip_serializing_if = "Option::is_none")]
- cur_primary_signature: Option<String>,
- /// Previous primary device signature. Present only
- /// if primary device has changed since last update.
- #[serde(default)]
- #[serde(skip_serializing_if = "Option::is_none")]
- last_primary_signature: Option<String>,
-}
-
-impl SignedDeviceList {
- /// Serialize (and sign in the future) a [`RawDeviceList`]
- fn try_from_raw(raw: RawDeviceList) -> Result<Self, tonic::Status> {
- let stringified_list = serde_json::to_string(&raw).map_err(|err| {
- error!(
- errorType = error_types::GRPC_SERVICES_LOG,
- "Failed to serialize raw device list: {}", err
- );
- tonic::Status::failed_precondition("unexpected error")
- })?;
-
- Ok(Self {
- raw_device_list: stringified_list,
- cur_primary_signature: None,
- last_primary_signature: None,
- })
- }
-
- fn as_raw(&self) -> Result<RawDeviceList, tonic::Status> {
- // The device list payload is sent as an escaped JSON payload.
- // Escaped double quotes need to be trimmed before attempting to deserialize
- serde_json::from_str(&self.raw_device_list.replace(r#"\""#, r#"""#))
- .map_err(|err| {
- warn!("Failed to deserialize raw device list: {}", err);
- tonic::Status::invalid_argument("invalid device list payload")
- })
- }
-
- /// Serializes the signed device list to a JSON string
- fn as_json_string(&self) -> Result<String, tonic::Status> {
- serde_json::to_string(self).map_err(|err| {
- error!(
- errorType = error_types::GRPC_SERVICES_LOG,
- "Failed to serialize device list updates: {}", err
- );
- tonic::Status::failed_precondition("unexpected error")
- })
- }
-}
-
-impl TryFrom<UpdateDeviceListRequest> for SignedDeviceList {
- type Error = tonic::Status;
- fn try_from(request: UpdateDeviceListRequest) -> Result<Self, Self::Error> {
- serde_json::from_str(&request.new_device_list).map_err(|err| {
- warn!("Failed to deserialize device list update: {}", err);
- tonic::Status::invalid_argument("invalid device list payload")
- })
- }
-}
-
-impl TryFrom<SignedDeviceList> for DeviceListUpdate {
- type Error = tonic::Status;
- fn try_from(signed_list: SignedDeviceList) -> Result<Self, Self::Error> {
- let RawDeviceList {
- devices,
- timestamp: raw_timestamp,
- } = signed_list.as_raw()?;
- let timestamp = DateTime::<Utc>::from_utc_timestamp_millis(raw_timestamp)
- .ok_or_else(|| {
- error!(
- errorType = error_types::GRPC_SERVICES_LOG,
- "Failed to parse RawDeviceList timestamp!"
- );
- tonic::Status::invalid_argument("invalid timestamp")
- })?;
- Ok(DeviceListUpdate::new(devices, timestamp))
- }
-}
-
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct DeletePasswordUserInfo {
pub opaque_server_login: comm_opaque2::server::Login,
@@ -748,116 +646,3 @@
opaque_server_login,
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn deserialize_device_list_signature() {
- let payload_with_signature = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}","curPrimarySignature":"foo"}"#;
- let payload_without_signatures = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":222222222}"}"#;
-
- let list_with_signature: SignedDeviceList =
- serde_json::from_str(payload_with_signature).unwrap();
- let list_without_signatures: SignedDeviceList =
- serde_json::from_str(payload_without_signatures).unwrap();
-
- assert_eq!(
- list_with_signature.cur_primary_signature,
- Some("foo".to_string())
- );
- assert!(list_with_signature.last_primary_signature.is_none());
-
- assert!(list_without_signatures.cur_primary_signature.is_none());
- assert!(list_without_signatures.last_primary_signature.is_none());
- }
-
- #[test]
- fn serialize_device_list_signatures() {
- let raw_list = r#"{"devices":["device1"],"timestamp":111111111}"#;
-
- let expected_payload_without_signatures = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}"}"#;
- let device_list_without_signature = SignedDeviceList {
- raw_device_list: raw_list.to_string(),
- cur_primary_signature: None,
- last_primary_signature: None,
- };
- assert_eq!(
- device_list_without_signature.as_json_string().unwrap(),
- expected_payload_without_signatures
- );
-
- let expected_payload_with_signature = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}","curPrimarySignature":"foo"}"#;
- let device_list_with_cur_signature = SignedDeviceList {
- raw_device_list: raw_list.to_string(),
- cur_primary_signature: Some("foo".to_string()),
- last_primary_signature: None,
- };
- assert_eq!(
- device_list_with_cur_signature.as_json_string().unwrap(),
- expected_payload_with_signature
- );
- }
-
- #[test]
- fn serialize_device_list_updates() {
- let raw_updates = vec![
- RawDeviceList {
- devices: vec!["device1".into()],
- timestamp: 111111111,
- },
- RawDeviceList {
- devices: vec!["device1".into(), "device2".into()],
- timestamp: 222222222,
- },
- ];
-
- let expected_raw_list1 = r#"{"devices":["device1"],"timestamp":111111111}"#;
- let expected_raw_list2 =
- r#"{"devices":["device1","device2"],"timestamp":222222222}"#;
-
- let signed_updates = raw_updates
- .into_iter()
- .map(SignedDeviceList::try_from_raw)
- .collect::<Result<Vec<_>, _>>()
- .expect("signing device list updates failed");
-
- assert_eq!(signed_updates[0].raw_device_list, expected_raw_list1);
- assert_eq!(signed_updates[1].raw_device_list, expected_raw_list2);
-
- let stringified_updates = signed_updates
- .iter()
- .map(serde_json::to_string)
- .collect::<Result<Vec<_>, _>>()
- .expect("serialize signed device lists failed");
-
- let expected_stringified_list1 = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}"}"#;
- let expected_stringified_list2 = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":222222222}"}"#;
-
- assert_eq!(stringified_updates[0], expected_stringified_list1);
- assert_eq!(stringified_updates[1], expected_stringified_list2);
- }
-
- #[test]
- fn deserialize_device_list_update() {
- let raw_payload = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":123456789}"}"#;
- let request = UpdateDeviceListRequest {
- new_device_list: raw_payload.to_string(),
- };
-
- let signed_list = SignedDeviceList::try_from(request)
- .expect("Failed to parse SignedDeviceList");
- let update = DeviceListUpdate::try_from(signed_list)
- .expect("Failed to parse DeviceListUpdate from signed list");
-
- let expected_timestamp =
- DateTime::<Utc>::from_utc_timestamp_millis(123456789).unwrap();
-
- assert_eq!(update.timestamp, expected_timestamp);
- assert_eq!(
- update.devices,
- vec!["device1".to_string(), "device2".to_string()]
- );
- }
-}

File Metadata

Mime Type
text/plain
Expires
Wed, Dec 25, 8:40 AM (11 h, 8 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2702103
Default Alt Text
D11896.diff (16 KB)

Event Timeline