Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3528225
D11896.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D11896.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D11896: [identity] Move device list structs to device_list.rs
Attached
Detach File
Event Timeline
Log In to Comment