Page MenuHomePhorge

D10799.1767347309.diff
No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None

D10799.1767347309.diff

diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs
--- a/services/identity/src/database/device_list.rs
+++ b/services/identity/src/database/device_list.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use chrono::{DateTime, Utc};
use comm_lib::{
@@ -14,7 +14,7 @@
DynamoDBError, TryFromAttribute,
},
};
-use tracing::{error, warn};
+use tracing::{debug, error, warn};
use crate::{
client_service::FlattenedDeviceKeyUpload,
@@ -66,9 +66,10 @@
/// A struct representing device list update request
/// payload; issued by the primary device
-#[derive(serde::Deserialize)]
+#[derive(derive_more::Constructor)]
pub struct DeviceListUpdate {
- devices: Vec<String>,
+ pub devices: Vec<String>,
+ pub timestamp: DateTime<Utc>,
}
impl DeviceRow {
@@ -725,6 +726,39 @@
Ok(())
}
+ /// applies updated device list received from primary device
+ pub async fn apply_devicelist_update(
+ &self,
+ user_id: &str,
+ update: DeviceListUpdate,
+ ) -> Result<DeviceListRow, Error> {
+ let DeviceListUpdate {
+ devices: new_list, ..
+ } = update;
+ self
+ .transact_update_devicelist(user_id, |current_list, _| {
+ // TODO: Add proper validation according to the whitepaper
+ // currently only adding new device is supported (new.len - old.len = 1)
+
+ let new_set: HashSet<_> = new_list.iter().collect();
+ let current_set: HashSet<_> = current_list.iter().collect();
+ // difference is A - B (only new devices)
+ let difference: HashSet<_> = new_set.difference(&current_set).collect();
+ if difference.len() != 1 {
+ warn!("Received invalid device list update");
+ return Err(Error::DeviceList(
+ DeviceListError::InvalidDeviceListUpdate,
+ ));
+ }
+
+ debug!("Applying device list update. Difference: {:?}", difference);
+ *current_list = new_list;
+
+ Ok(None)
+ })
+ .await
+ }
+
/// Performs a transactional update of the device list for the user. Afterwards
/// generates a new device list and updates the timestamp in the users table.
/// This is done in a transaction. Operation fails if the device list has been
diff --git a/services/identity/src/error.rs b/services/identity/src/error.rs
--- a/services/identity/src/error.rs
+++ b/services/identity/src/error.rs
@@ -27,6 +27,7 @@
DeviceAlreadyExists,
DeviceNotFound,
ConcurrentUpdateError,
+ InvalidDeviceListUpdate,
}
pub fn consume_error<T>(result: Result<T, Error>) {
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,8 +1,7 @@
use std::collections::HashMap;
use crate::config::CONFIG;
-use crate::database::DeviceListRow;
-use crate::ddb_utils::Identifier;
+use crate::database::{DeviceListRow, DeviceListUpdate};
use crate::{
client_service::{
handle_db_error, CacheExt, UpdateState, WorkflowInProgress,
@@ -17,7 +16,7 @@
use comm_opaque2::grpc::protocol_error_to_grpc_status;
use moka::future::Cache;
use tonic::{Request, Response, Status};
-use tracing::{debug, error};
+use tracing::{debug, error, warn};
use super::protos::auth::{
find_user_id_request, identity,
@@ -416,9 +415,21 @@
async fn update_device_list_for_user(
&self,
- _request: tonic::Request<UpdateDeviceListRequest>,
+ request: tonic::Request<UpdateDeviceListRequest>,
) -> Result<Response<Empty>, tonic::Status> {
- Err(tonic::Status::unimplemented("not implemented"))
+ let (user_id, _device_id) = get_user_and_device_id(&request)?;
+ // TODO: when we stop doing "primary device rotation" (migration procedure)
+ // we should verify if this RPC is called by primary device only
+
+ let new_list = SignedDeviceList::try_from(request.into_inner())?;
+ let update = DeviceListUpdate::try_from(new_list)?;
+ self
+ .db_client
+ .apply_devicelist_update(&user_id, update)
+ .await
+ .map_err(handle_db_error)?;
+
+ Ok(Response::new(Empty {}))
}
}
@@ -457,18 +468,44 @@
raw_device_list: stringified_list,
})
}
+
+ 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")
+ })
+ }
}
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| {
- error!("Failed to deserialize device list update: {}", err);
- tonic::Status::failed_precondition("unexpected error")
+ 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!("Failed to parse RawDeviceList timestamp!");
+ tonic::Status::invalid_argument("invalid timestamp")
+ })?;
+ Ok(DeviceListUpdate::new(devices, timestamp))
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -511,4 +548,26 @@
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
Fri, Jan 2, 9:48 AM (6 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5879002
Default Alt Text
D10799.1767347309.diff (6 KB)

Event Timeline