Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32569769
D10799.1767379375.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
D10799.1767379375.diff
View Options
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(¤t_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
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 2, 6:42 PM (11 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5879002
Default Alt Text
D10799.1767379375.diff (6 KB)
Attached To
Mode
D10799: [identity] Basic implementation of UpdateDeviceList RPC
Attached
Detach File
Event Timeline
Log In to Comment