From ed9b544ace12159f8a9267e73f401ca949b5792d Mon Sep 17 00:00:00 2001 From: gnieto Date: Wed, 3 Jun 2020 13:41:30 +0200 Subject: [PATCH] Implement devices API (#20) small improvements Cargo fmt Simplify insert and update methods Review feedback Remove has_device method calls Load all devices with a single db call Remove device as in logout Put all metadata on the same tree Create userdevice key fucntion Implement devices API Implement all the devices endpoints. There's a couple of pending tasks: - Integrate the "logout" logic once it lands to master (this should remove the given device from the database). - Track and store last seen timestamp and IP. Co-authored-by: timokoesters Co-authored-by: Guillem Nieto --- src/client_server.rs | 99 +++++++++++++++++++++++++++++++++++-- src/database.rs | 2 +- src/database/users.rs | 110 ++++++++++++++++++++++++++++++++++-------- src/main.rs | 5 ++ 4 files changed, 190 insertions(+), 26 deletions(-) diff --git a/src/client_server.rs b/src/client_server.rs index 2cb7a5f..5f5070e 100644 --- a/src/client_server.rs +++ b/src/client_server.rs @@ -13,6 +13,9 @@ use ruma_client_api::{ alias::{create_alias, delete_alias, get_alias}, capabilities::get_capabilities, config::{get_global_account_data, set_global_account_data}, + device::{ + self, delete_device, delete_devices, get_device, get_devices, update_device, + }, directory::{ self, get_public_rooms, get_public_rooms_filtered, get_room_visibility, set_room_visibility, @@ -52,7 +55,7 @@ use ruma_events::{ room::{canonical_alias, guest_access, history_visibility, join_rules, member, redaction}, EventJson, EventType, }; -use ruma_identifiers::{RoomAliasId, RoomId, RoomVersionId, UserId}; +use ruma_identifiers::{DeviceId, RoomAliasId, RoomId, RoomVersionId, UserId}; use serde_json::{json, value::RawValue}; use crate::{server_server, utils, Database, MatrixResult, Ruma}; @@ -173,7 +176,7 @@ pub fn register_route( // Generate new device id if the user didn't specify one let device_id = body - .device_id + .device_id.clone() .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH)); // Generate new token for the device @@ -181,7 +184,7 @@ pub fn register_route( // Add device db.users - .create_device(&user_id, &device_id, &token) + .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone()) .unwrap(); // Initial data @@ -300,6 +303,7 @@ pub fn login_route( let device_id = body .body .device_id + .clone() .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH)); // Generate a new token for the device @@ -307,7 +311,7 @@ pub fn login_route( // Add device db.users - .create_device(&user_id, &device_id, &token) + .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone()) .unwrap(); MatrixResult(Ok(login::Response { @@ -2430,6 +2434,93 @@ pub fn get_content_thumbnail_route( } } +#[get("/_matrix/client/r0/devices", data = "")] +pub fn get_devices_route( + db: State<'_, Database>, + body: Ruma, +) -> MatrixResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + + let devices = db + .users + .all_devices_metadata(user_id) + .map(|r| r.unwrap()) + .collect::>(); + + MatrixResult(Ok(get_devices::Response { devices })) +} + +#[get("/_matrix/client/r0/devices/", data = "")] +pub fn get_device_route( + db: State<'_, Database>, + body: Ruma, + device_id: DeviceId, +) -> MatrixResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + let device = db.users.get_device_metadata(&user_id, &device_id).unwrap(); + + match device { + None => MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "Device not found".to_string(), + status_code: http::StatusCode::NOT_FOUND, + })), + Some(device) => MatrixResult(Ok(get_device::Response { device })), + } +} + +#[put("/_matrix/client/r0/devices/", data = "")] +pub fn update_device_route( + db: State<'_, Database>, + body: Ruma, + device_id: DeviceId, +) -> MatrixResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + let device = db.users.get_device_metadata(&user_id, &device_id).unwrap(); + + match device { + None => MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "Device not found".to_string(), + status_code: http::StatusCode::NOT_FOUND, + })), + Some(mut device) => { + device.display_name = body.display_name.clone(); + + db.users + .update_device_metadata(&user_id, &device_id, &device) + .unwrap(); + + MatrixResult(Ok(update_device::Response)) + } + } +} + +#[delete("/_matrix/client/r0/devices/", data = "")] +pub fn delete_device_route( + db: State<'_, Database>, + body: Ruma, + device_id: DeviceId, +) -> MatrixResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + db.users.remove_device(&user_id, &device_id).unwrap(); + + MatrixResult(Ok(delete_device::Response)) +} + +#[post("/_matrix/client/r0/delete_devices", data = "")] +pub fn delete_devices_route( + db: State<'_, Database>, + body: Ruma, +) -> MatrixResult { + let user_id = body.user_id.as_ref().expect("user is authenticated"); + for device_id in &body.devices { + db.users.remove_device(&user_id, &device_id).unwrap() + } + + MatrixResult(Ok(delete_devices::Response)) +} + #[options("/<_segments..>")] pub fn options_route( _segments: rocket::http::uri::Segments<'_>, diff --git a/src/database.rs b/src/database.rs index de14805..d4927a7 100644 --- a/src/database.rs +++ b/src/database.rs @@ -56,10 +56,10 @@ impl Database { ), users: users::Users { userid_password: db.open_tree("userid_password").unwrap(), - userdeviceids: db.open_tree("userdeviceids").unwrap(), userid_displayname: db.open_tree("userid_displayname").unwrap(), userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(), userdeviceid_token: db.open_tree("userdeviceid_token").unwrap(), + userdeviceid_metadata: db.open_tree("userdeviceid_metadata").unwrap(), token_userdeviceid: db.open_tree("token_userdeviceid").unwrap(), onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys").unwrap(), userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys").unwrap(), diff --git a/src/database/users.rs b/src/database/users.rs index 6540a70..e216301 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -1,16 +1,19 @@ use crate::{utils, Error, Result}; use js_int::UInt; -use ruma_client_api::r0::keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey}; +use ruma_client_api::r0::{ + device::Device, + keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey}, +}; use ruma_events::{to_device::AnyToDeviceEvent, EventJson, EventType}; use ruma_identifiers::{DeviceId, UserId}; -use std::{collections::BTreeMap, convert::TryFrom}; +use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime}; pub struct Users { pub(super) userid_password: sled::Tree, pub(super) userid_displayname: sled::Tree, pub(super) userid_avatarurl: sled::Tree, - pub(super) userdeviceids: sled::Tree, pub(super) userdeviceid_token: sled::Tree, + pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists pub(super) token_userdeviceid: sled::Tree, pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId @@ -105,25 +108,40 @@ impl Users { } /// Adds a new device to a user. - pub fn create_device(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> { + pub fn create_device( + &self, + user_id: &UserId, + device_id: &DeviceId, + token: &str, + initial_device_display_name: Option, + ) -> Result<()> { if !self.exists(user_id)? { return Err(Error::BadRequest( "tried to create device for nonexistent user", )); } - let mut key = user_id.to_string().as_bytes().to_vec(); - key.push(0xff); - key.extend_from_slice(device_id.as_bytes()); + let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); + userdeviceid.push(0xff); + userdeviceid.extend_from_slice(device_id.as_bytes()); - self.userdeviceids.insert(key, &[])?; + self.userdeviceid_metadata.insert( + userdeviceid, + serde_json::to_string(&Device { + device_id: device_id.clone(), + display_name: initial_device_display_name, + last_seen_ip: None, // TODO + last_seen_ts: Some(SystemTime::now()), + })? + .as_bytes(), + )?; self.set_token(user_id, device_id, token)?; Ok(()) } - /// Removes a device from a user + /// Removes a device from a user. pub fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> { let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); userdeviceid.push(0xff); @@ -147,8 +165,7 @@ impl Users { // TODO: Remove onetimekeys - // Remove the device - self.userdeviceids.remove(userdeviceid)?; + self.userdeviceid_metadata.remove(&userdeviceid)?; Ok(()) } @@ -157,14 +174,18 @@ impl Users { pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator> { let mut prefix = user_id.to_string().as_bytes().to_vec(); prefix.push(0xff); - self.userdeviceids.scan_prefix(prefix).keys().map(|bytes| { - Ok(utils::string_from_bytes( - &*bytes? - .rsplit(|&b| b == 0xff) - .next() - .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, - )?) - }) + // All devices have metadata + self.userdeviceid_metadata + .scan_prefix(prefix) + .keys() + .map(|bytes| { + Ok(utils::string_from_bytes( + &*bytes? + .rsplit(|&b| b == 0xff) + .next() + .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, + )?) + }) } /// Replaces the access token of one device. @@ -173,7 +194,8 @@ impl Users { userdeviceid.push(0xff); userdeviceid.extend_from_slice(device_id.as_bytes()); - if self.userdeviceids.get(&userdeviceid)?.is_none() { + // All devices have metadata + if self.userdeviceid_metadata.get(&userdeviceid)?.is_none() { return Err(Error::BadRequest( "Tried to set token for nonexistent device", )); @@ -203,7 +225,8 @@ impl Users { key.push(0xff); key.extend_from_slice(device_id.as_bytes()); - if self.userdeviceids.get(&key)?.is_none() { + // All devices have metadata + if self.userdeviceid_metadata.get(&key)?.is_none() { return Err(Error::BadRequest( "Tried to set token for nonexistent device", )); @@ -396,4 +419,49 @@ impl Users { Ok(events) } + + pub fn update_device_metadata( + &self, + user_id: &UserId, + device_id: &DeviceId, + device: &Device, + ) -> Result<()> { + let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); + userdeviceid.push(0xff); + userdeviceid.extend_from_slice(device_id.as_bytes()); + + if self.userdeviceid_metadata.get(userdeviceid)?.is_none() { + return Err(Error::BadRequest("device does not exist")); + } + + self.userdeviceid_metadata + .insert(userdeviceid, serde_json::to_string(device)?.as_bytes())?; + + Ok(()) + } + + /// Get device metadata. + pub fn get_device_metadata( + &self, + user_id: &UserId, + device_id: &DeviceId, + ) -> Result> { + let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); + userdeviceid.push(0xff); + userdeviceid.extend_from_slice(device_id.as_bytes()); + + self.userdeviceid_metadata + .get(&userdeviceid)? + .map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes)?))) + } + + pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + + self.userdeviceid_metadata + .scan_prefix(key) + .values() + .map(|bytes| Ok(serde_json::from_slice::(&bytes?)?)) + } } diff --git a/src/main.rs b/src/main.rs index 4146282..c4a630b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,11 @@ fn setup_rocket() -> rocket::Rocket { client_server::create_content_route, client_server::get_content_route, client_server::get_content_thumbnail_route, + client_server::get_devices_route, + client_server::get_device_route, + client_server::update_device_route, + client_server::delete_device_route, + client_server::delete_devices_route, client_server::options_route, server_server::well_known_server, server_server::get_server_version,