From 38663228f524d15fae9c4b3d1e7c64a7bf61d308 Mon Sep 17 00:00:00 2001 From: Timo Date: Sun, 23 Aug 2020 16:47:27 +0200 Subject: [PATCH 1/9] fix: put reason of redaction in the redacted event --- src/database/rooms.rs | 22 ++++------------------ src/pdu.rs | 4 ++-- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/database/rooms.rs b/src/database/rooms.rs index d2cd5e9..3c1febd 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -11,7 +11,6 @@ use ruma::{ room::{ join_rules, member, power_levels::{self, PowerLevelsEventContent}, - redaction, }, EventType, }, @@ -566,7 +565,7 @@ impl Rooms { self.eventid_pduid .insert(pdu.event_id.to_string(), pdu_id.clone())?; - if let Some(state_key) = pdu.state_key { + if let Some(state_key) = &pdu.state_key { let mut key = room_id.to_string().as_bytes().to_vec(); key.push(0xff); key.extend_from_slice(pdu.kind.to_string().as_bytes()); @@ -578,20 +577,7 @@ impl Rooms { match event_type { EventType::RoomRedaction => { if let Some(redact_id) = &redacts { - // TODO: Reason - let _reason = - serde_json::from_value::>(content) - .expect("Raw::from_value always works.") - .deserialize() - .map_err(|_| { - Error::BadRequest( - ErrorKind::InvalidParam, - "Invalid redaction event content.", - ) - })? - .reason; - - self.redact_pdu(&redact_id)?; + self.redact_pdu(&redact_id, &pdu)?; } } EventType::RoomMember => { @@ -758,12 +744,12 @@ impl Rooms { } /// Replace a PDU with the redacted form. - pub fn redact_pdu(&self, event_id: &EventId) -> Result<()> { + pub fn redact_pdu(&self, event_id: &EventId, reason: &PduEvent) -> Result<()> { if let Some(pdu_id) = self.get_pdu_id(event_id)? { let mut pdu = self .get_pdu_from_id(&pdu_id)? .ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?; - pdu.redact()?; + pdu.redact(&reason)?; self.replace_pdu(&pdu_id, &pdu)?; Ok(()) } else { diff --git a/src/pdu.rs b/src/pdu.rs index 9936802..4458423 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -35,7 +35,7 @@ pub struct PduEvent { } impl PduEvent { - pub fn redact(&mut self) -> Result<()> { + pub fn redact(&mut self, reason: &PduEvent) -> Result<()> { self.unsigned.clear(); let allowed: &[&str] = match self.kind { @@ -71,7 +71,7 @@ impl PduEvent { self.unsigned.insert( "redacted_because".to_owned(), - json!({"content": {}, "type": "m.room.redaction"}), + serde_json::to_string(reason).expect("PduEvent::to_string always works").into() ); self.content = new_content.into(); From 33215d6099e7aa4728c240c26d243b3862004197 Mon Sep 17 00:00:00 2001 From: Timo Date: Sun, 23 Aug 2020 17:29:39 +0200 Subject: [PATCH 2/9] fix: send notification count updates when private read receipts change --- src/client_server/read_marker.rs | 5 +- src/client_server/sync.rs | 20 ++--- src/client_server/typing.rs | 4 +- src/database.rs | 13 +-- src/database/rooms.rs | 2 +- src/database/rooms/edus.rs | 148 ++++++++++++++++++------------- 6 files changed, 107 insertions(+), 85 deletions(-) diff --git a/src/client_server/read_marker.rs b/src/client_server/read_marker.rs index ff72765..1b8bd8e 100644 --- a/src/client_server/read_marker.rs +++ b/src/client_server/read_marker.rs @@ -34,13 +34,14 @@ pub fn set_read_marker_route( )?; if let Some(event) = &body.read_receipt { - db.rooms.edus.room_read_set( + db.rooms.edus.private_read_set( &body.room_id, &sender_id, db.rooms.get_pdu_count(event)?.ok_or(Error::BadRequest( ErrorKind::InvalidParam, "Event does not exist.", ))?, + &db.globals, )?; let mut user_receipts = BTreeMap::new(); @@ -58,7 +59,7 @@ pub fn set_read_marker_route( }, ); - db.rooms.edus.roomlatest_update( + db.rooms.edus.readreceipt_update( &sender_id, &body.room_id, AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt( diff --git a/src/client_server/sync.rs b/src/client_server/sync.rs index 2307f02..8f37354 100644 --- a/src/client_server/sync.rs +++ b/src/client_server/sync.rs @@ -81,7 +81,12 @@ pub async fn sync_events_route( .rev() .collect::>(); - let send_notification_counts = !timeline_pdus.is_empty(); + let send_notification_counts = !timeline_pdus.is_empty() + || db + .rooms + .edus + .last_privateread_update(&sender_id, &room_id)? + > since; // They /sync response doesn't always return all messages, so we say the output is // limited unless there are events in non_timeline_pdus @@ -242,7 +247,7 @@ pub async fn sync_events_route( }; let notification_count = if send_notification_counts { - if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &sender_id)? { + if let Some(last_read) = db.rooms.edus.private_read_get(&room_id, &sender_id)? { Some( (db.rooms .pdus_since(&sender_id, &room_id, last_read)? @@ -280,20 +285,15 @@ pub async fn sync_events_route( let mut edus = db .rooms .edus - .roomlatests_since(&room_id, since)? + .readreceipts_since(&room_id, since)? .filter_map(|r| r.ok()) // Filter out buggy events .collect::>(); - if db - .rooms - .edus - .last_roomactive_update(&room_id, &db.globals)? - > since - { + if db.rooms.edus.last_typing_update(&room_id, &db.globals)? > since { edus.push( serde_json::from_str( &serde_json::to_string(&AnySyncEphemeralRoomEvent::Typing( - db.rooms.edus.roomactives_all(&room_id)?, + db.rooms.edus.typings_all(&room_id)?, )) .expect("event is valid, we just created it"), ) diff --git a/src/client_server/typing.rs b/src/client_server/typing.rs index 7eba13e..89e1e4a 100644 --- a/src/client_server/typing.rs +++ b/src/client_server/typing.rs @@ -16,7 +16,7 @@ pub fn create_typing_event_route( let sender_id = body.sender_id.as_ref().expect("user is authenticated"); if body.typing { - db.rooms.edus.roomactive_add( + db.rooms.edus.typing_add( &sender_id, &body.room_id, body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000) @@ -26,7 +26,7 @@ pub fn create_typing_event_route( } else { db.rooms .edus - .roomactive_remove(&sender_id, &body.room_id, &db.globals)?; + .typing_remove(&sender_id, &body.room_id, &db.globals)?; } Ok(create_typing_event::Response.into()) diff --git a/src/database.rs b/src/database.rs index 7bbb6dd..41781b9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -88,10 +88,11 @@ impl Database { }, rooms: rooms::Rooms { edus: rooms::RoomEdus { - roomuserid_lastread: db.open_tree("roomuserid_lastread")?, // "Private" read receipt - roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest")?, // Read receipts - roomactiveid_userid: db.open_tree("roomactiveid_userid")?, // Typing notifs - roomid_lastroomactiveupdate: db.open_tree("roomid_lastroomactiveupdate")?, + readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?, + roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt + roomuserid_lastprivatereadupdate: db.open_tree("roomid_lastprivatereadupdate")?, + typingid_userid: db.open_tree("typingid_userid")?, + roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?, presenceid_presence: db.open_tree("presenceid_presence")?, userid_lastpresenceupdate: db.open_tree("userid_lastpresenceupdate")?, }, @@ -163,14 +164,14 @@ impl Database { futures.push( self.rooms .edus - .roomid_lastroomactiveupdate + .roomid_lasttypingupdate .watch_prefix(&roomid_bytes), ); futures.push( self.rooms .edus - .roomlatestid_roomlatest + .readreceiptid_readreceipt .watch_prefix(&roomid_prefix), ); diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 3c1febd..bb14c8a 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -621,7 +621,7 @@ impl Rooms { } _ => {} } - self.edus.room_read_set(&room_id, &sender, index)?; + self.edus.private_read_set(&room_id, &sender, index, &globals)?; Ok(pdu.event_id) } diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs index fff30c2..fbd3edb 100644 --- a/src/database/rooms/edus.rs +++ b/src/database/rooms/edus.rs @@ -14,17 +14,18 @@ use std::{ }; pub struct RoomEdus { - pub(in super::super) roomuserid_lastread: sled::Tree, // RoomUserId = Room + User - pub(in super::super) roomlatestid_roomlatest: sled::Tree, // Read Receipts, RoomLatestId = RoomId + Count + UserId - pub(in super::super) roomactiveid_userid: sled::Tree, // Typing, RoomActiveId = RoomId + TimeoutTime + Count - pub(in super::super) roomid_lastroomactiveupdate: sled::Tree, // LastRoomActiveUpdate = Count + pub(in super::super) readreceiptid_readreceipt: sled::Tree, // ReadReceiptId = RoomId + Count + UserId + pub(in super::super) roomuserid_privateread: sled::Tree, // RoomUserId = Room + User, PrivateRead = Count + pub(in super::super) roomuserid_lastprivatereadupdate: sled::Tree, // LastPrivateReadUpdate = Count + pub(in super::super) typingid_userid: sled::Tree, // TypingId = RoomId + TimeoutTime + Count + pub(in super::super) roomid_lasttypingupdate: sled::Tree, // LastRoomTypingUpdate = Count pub(in super::super) presenceid_presence: sled::Tree, // PresenceId = RoomId + Count + UserId pub(in super::super) userid_lastpresenceupdate: sled::Tree, // LastPresenceUpdate = Count } impl RoomEdus { /// Adds an event which will be saved until a new event replaces it (e.g. read receipt). - pub fn roomlatest_update( + pub fn readreceipt_update( &self, user_id: &UserId, room_id: &RoomId, @@ -36,7 +37,7 @@ impl RoomEdus { // Remove old entry if let Some(old) = self - .roomlatestid_roomlatest + .readreceiptid_readreceipt .scan_prefix(&prefix) .keys() .rev() @@ -50,7 +51,7 @@ impl RoomEdus { }) { // This is the old room_latest - self.roomlatestid_roomlatest.remove(old)?; + self.readreceiptid_readreceipt.remove(old)?; } let mut room_latest_id = prefix; @@ -58,7 +59,7 @@ impl RoomEdus { room_latest_id.push(0xff); room_latest_id.extend_from_slice(&user_id.to_string().as_bytes()); - self.roomlatestid_roomlatest.insert( + self.readreceiptid_readreceipt.insert( room_latest_id, &*serde_json::to_string(&event).expect("EduEvent::to_string always works"), )?; @@ -67,7 +68,7 @@ impl RoomEdus { } /// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`. - pub fn roomlatests_since( + pub fn readreceipts_since( &self, room_id: &RoomId, since: u64, @@ -79,7 +80,7 @@ impl RoomEdus { first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since Ok(self - .roomlatestid_roomlatest + .readreceiptid_readreceipt .range(&*first_possible_edu..) .filter_map(|r| r.ok()) .take_while(move |(k, _)| k.starts_with(&prefix)) @@ -90,9 +91,54 @@ impl RoomEdus { })) } - /// Sets a user as typing until the timeout timestamp is reached or roomactive_remove is + /// Sets a private read marker at `count`. + pub fn private_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64, globals: &super::super::globals::Globals) -> Result<()> { + let mut key = room_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&user_id.to_string().as_bytes()); + + self.roomuserid_privateread + .insert(&key, &count.to_be_bytes())?; + + self.roomuserid_lastprivatereadupdate + .insert(&key, &globals.next_count()?.to_be_bytes())?; + + Ok(()) + } + + /// Returns the private read marker. + pub fn private_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result> { + let mut key = room_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&user_id.to_string().as_bytes()); + + self.roomuserid_privateread.get(key)?.map_or(Ok(None), |v| { + Ok(Some(utils::u64_from_bytes(&v).map_err(|_| { + Error::bad_database("Invalid private read marker bytes") + })?)) + }) + } + + /// Returns the count of the last typing update in this room. + pub fn last_privateread_update(&self, user_id: &UserId, room_id: &RoomId) -> Result { + let mut key = room_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&user_id.to_string().as_bytes()); + + Ok(self + .roomuserid_lastprivatereadupdate + .get(&key)? + .map_or(Ok::<_, Error>(None), |bytes| { + Ok(Some(utils::u64_from_bytes(&bytes).map_err(|_| { + Error::bad_database("Count in roomuserid_lastprivatereadupdate is invalid.") + })?)) + })? + .unwrap_or(0)) + } + + /// Sets a user as typing until the timeout timestamp is reached or roomtyping_remove is /// called. - pub fn roomactive_add( + pub fn typing_add( &self, user_id: &UserId, room_id: &RoomId, @@ -104,22 +150,22 @@ impl RoomEdus { let count = globals.next_count()?.to_be_bytes(); - let mut room_active_id = prefix; - room_active_id.extend_from_slice(&timeout.to_be_bytes()); - room_active_id.push(0xff); - room_active_id.extend_from_slice(&count); + let mut room_typing_id = prefix; + room_typing_id.extend_from_slice(&timeout.to_be_bytes()); + room_typing_id.push(0xff); + room_typing_id.extend_from_slice(&count); - self.roomactiveid_userid - .insert(&room_active_id, &*user_id.to_string().as_bytes())?; + self.typingid_userid + .insert(&room_typing_id, &*user_id.to_string().as_bytes())?; - self.roomid_lastroomactiveupdate + self.roomid_lasttypingupdate .insert(&room_id.to_string().as_bytes(), &count)?; Ok(()) } /// Removes a user from typing before the timeout is reached. - pub fn roomactive_remove( + pub fn typing_remove( &self, user_id: &UserId, room_id: &RoomId, @@ -132,19 +178,19 @@ impl RoomEdus { let mut found_outdated = false; - // Maybe there are multiple ones from calling roomactive_add multiple times + // Maybe there are multiple ones from calling roomtyping_add multiple times for outdated_edu in self - .roomactiveid_userid + .typingid_userid .scan_prefix(&prefix) .filter_map(|r| r.ok()) .filter(|(_, v)| v == user_id.as_bytes()) { - self.roomactiveid_userid.remove(outdated_edu.0)?; + self.typingid_userid.remove(outdated_edu.0)?; found_outdated = true; } if found_outdated { - self.roomid_lastroomactiveupdate.insert( + self.roomid_lasttypingupdate.insert( &room_id.to_string().as_bytes(), &globals.next_count()?.to_be_bytes(), )?; @@ -154,7 +200,7 @@ impl RoomEdus { } /// Makes sure that typing events with old timestamps get removed. - fn roomactives_maintain( + fn typings_maintain( &self, room_id: &RoomId, globals: &super::super::globals::Globals, @@ -168,7 +214,7 @@ impl RoomEdus { // Find all outdated edus before inserting a new one for outdated_edu in self - .roomactiveid_userid + .typingid_userid .scan_prefix(&prefix) .keys() .map(|key| { @@ -176,21 +222,21 @@ impl RoomEdus { Ok::<_, Error>(( key.clone(), utils::u64_from_bytes(key.split(|&b| b == 0xff).nth(1).ok_or_else(|| { - Error::bad_database("RoomActive has invalid timestamp or delimiters.") + Error::bad_database("RoomTyping has invalid timestamp or delimiters.") })?) - .map_err(|_| Error::bad_database("RoomActive has invalid timestamp bytes."))?, + .map_err(|_| Error::bad_database("RoomTyping has invalid timestamp bytes."))?, )) }) .filter_map(|r| r.ok()) .take_while(|&(_, timestamp)| timestamp < current_timestamp) { // This is an outdated edu (time > timestamp) - self.roomactiveid_userid.remove(outdated_edu.0)?; + self.typingid_userid.remove(outdated_edu.0)?; found_outdated = true; } if found_outdated { - self.roomid_lastroomactiveupdate.insert( + self.roomid_lasttypingupdate.insert( &room_id.to_string().as_bytes(), &globals.next_count()?.to_be_bytes(), )?; @@ -199,16 +245,16 @@ impl RoomEdus { Ok(()) } - /// Returns an iterator over all active events (e.g. typing notifications). - pub fn last_roomactive_update( + /// Returns the count of the last typing update in this room. + pub fn last_typing_update( &self, room_id: &RoomId, globals: &super::super::globals::Globals, ) -> Result { - self.roomactives_maintain(room_id, globals)?; + self.typings_maintain(room_id, globals)?; Ok(self - .roomid_lastroomactiveupdate + .roomid_lasttypingupdate .get(&room_id.to_string().as_bytes())? .map_or(Ok::<_, Error>(None), |bytes| { Ok(Some(utils::u64_from_bytes(&bytes).map_err(|_| { @@ -218,7 +264,7 @@ impl RoomEdus { .unwrap_or(0)) } - pub fn roomactives_all( + pub fn typings_all( &self, room_id: &RoomId, ) -> Result> { @@ -228,17 +274,15 @@ impl RoomEdus { let mut user_ids = Vec::new(); for user_id in self - .roomactiveid_userid + .typingid_userid .scan_prefix(prefix) .values() .map(|user_id| { Ok::<_, Error>( UserId::try_from(utils::string_from_bytes(&user_id?).map_err(|_| { - Error::bad_database("User ID in roomactiveid_userid is invalid unicode.") + Error::bad_database("User ID in typingid_userid is invalid unicode.") })?) - .map_err(|_| { - Error::bad_database("User ID in roomactiveid_userid is invalid.") - })?, + .map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))?, ) }) { @@ -250,30 +294,6 @@ impl RoomEdus { }) } - /// Sets a private read marker at `count`. - pub fn room_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64) -> Result<()> { - let mut key = room_id.to_string().as_bytes().to_vec(); - key.push(0xff); - key.extend_from_slice(&user_id.to_string().as_bytes()); - - self.roomuserid_lastread.insert(key, &count.to_be_bytes())?; - - Ok(()) - } - - /// Returns the private read marker. - pub fn room_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result> { - let mut key = room_id.to_string().as_bytes().to_vec(); - key.push(0xff); - key.extend_from_slice(&user_id.to_string().as_bytes()); - - self.roomuserid_lastread.get(key)?.map_or(Ok(None), |v| { - Ok(Some(utils::u64_from_bytes(&v).map_err(|_| { - Error::bad_database("Invalid private read marker bytes") - })?)) - }) - } - /// Adds a presence event which will be saved until a new event replaces it. /// /// Note: This method takes a RoomId because presence updates are always bound to rooms to From 0c1cc8d82bb1474f6585a516a01fbabe8b040a66 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 24 Aug 2020 10:45:57 +0200 Subject: [PATCH 3/9] Fix CI --- sytest/sytest-whitelist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sytest/sytest-whitelist b/sytest/sytest-whitelist index 3c1095b..b0b2097 100644 --- a/sytest/sytest-whitelist +++ b/sytest/sytest-whitelist @@ -17,6 +17,7 @@ Can invite users to invite-only rooms Can list tags for a room Can logout all devices Can logout current device +Can re-join room if re-invited Can read configuration endpoint Can recv a device message using /sync Can recv device messages until they are acknowledged @@ -113,7 +114,6 @@ Typing events appear in incremental sync Typing events appear in initial sync Uninvited users cannot join the room User appears in user directory -User directory correctly update on display name change User in dir while user still shares private rooms User in shared private room does appear in user directory User is offline if they set_presence=offline in their sync From 38ac3e42be87509ec04c35adfb65b3ba89daecbf Mon Sep 17 00:00:00 2001 From: Daniel Wiesenberg Date: Mon, 24 Aug 2020 23:30:39 +0200 Subject: [PATCH 4/9] Docker add healthcheck and mention Docker Hub image --- Dockerfile | 8 ++++++-- README.md | 9 ++++++++- docker-compose.yml | 23 ++++++++++++----------- docker/README.md | 8 ++++---- docker/docker-compose.traefik.yml | 23 ++++++++++++----------- 5 files changed, 42 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index fa4b16d..ff84ac6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,10 +53,10 @@ LABEL org.opencontainers.image.created=${CREATED} \ org.opencontainers.image.url="https://conduit.rs/" \ org.opencontainers.image.revision=${GIT_REF} \ org.opencontainers.image.source="https://git.koesters.xyz/timo/conduit.git" \ - org.opencontainers.image.documentation.="" \ org.opencontainers.image.licenses="AGPL-3.0-only" \ + org.opencontainers.image.documentation="" \ org.opencontainers.image.ref.name="" \ - org.label-schema.docker.build="docker build . -t conduit_homeserver:latest --build-arg CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)" \ + org.label-schema.docker.build="docker build . -t matrixconduit/matrix-conduit:latest --build-arg CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)" \ maintainer="Weasy666" # Standard port on which Rocket launches @@ -81,11 +81,15 @@ RUN chown -cR www-data:www-data /srv/conduit # Install packages needed to run Conduit RUN apk add --no-cache \ ca-certificates \ + curl \ libgcc # Create a volume for the database, to persist its contents VOLUME ["/srv/conduit/.local/share/conduit"] +# Test if Conduit is still alive, uses the same endpoint as Element +HEALTHCHECK --start-period=2s CMD curl --fail -s http://localhost:8000/_matrix/client/versions || curl -k --fail -s https://localhost:8000/_matrix/client/versions || exit 1 + # Set user to www-data USER www-data # Set container home directory diff --git a/README.md b/README.md index ad13089..44ab0d6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,14 @@ Clone the repo, build it with `cargo build --release` and call the binary ##### Using Docker -Build the docker image and run it with docker or docker-compose. [Read more](docker/README.md) +Pull and run the docker image with + +``` bash +docker pull matrixconduit/matrix-conduit:latest +docker run -d matrixconduit/matrix-conduit:latest -p 8448:8000 -v db:/srv/conduit/.local/share/conduit +``` + +Or build and run it with docker or docker-compose. [Read more](docker/README.md) #### What is it build on? diff --git a/docker-compose.yml b/docker-compose.yml index afd3699..f06eaca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,18 +3,19 @@ version: '3' services: homeserver: - ### If you already built the Conduit image with 'docker build', then you can uncomment the - ### 'image' line and comment out the 'build' option. - # image: conduit_homeserver:latest - ### If you want meaningful labels in you built Conduit image, you should run docker-compose like this: + ### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image, + ### then you are ready to go. + image: matrixconduit/matrix-conduit:latest + ### If you want to build a fresh image from the sources, then comment the image line and uncomment the + ### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this: ### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d - build: - context: . - args: - CREATED: - VERSION: - LOCAL: "false" - GIT_REF: HEAD + # build: + # context: . + # args: + # CREATED: + # VERSION: + # LOCAL: 'false' + # GIT_REF: HEAD restart: unless-stopped ports: - 8448:8000 diff --git a/docker/README.md b/docker/README.md index 5a6ecde..c569c5f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -28,10 +28,10 @@ ARG GIT_REF=HEAD To build the image you can use the following command ``` bash -docker build . -t conduit_homeserver:latest --build-arg CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) +docker build . -t matrixconduit/matrix-conduit:latest --build-arg CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) ``` -which also will tag the resulting image as `conduit_homeserver:latest`. +which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`. **Note:** it ommits the two optional `build-arg`s. @@ -40,7 +40,7 @@ which also will tag the resulting image as `conduit_homeserver:latest`. After building the image you can simply run it with ``` bash -docker run conduit_homeserver:latest -p 8448:8000 -v db:/srv/conduit/.local/share/conduit -e ROCKET_SERVER_NAME="localhost:8000" +docker run -d matrixconduit/matrix-conduit:latest -p 8448:8000 -v db:/srv/conduit/.local/share/conduit -e ROCKET_SERVER_NAME="localhost:8000" ``` For detached mode, you also need to use the `-d` flag. You can pass in more env vars as are shown here, for an overview of possible values, you can take a look at the `docker-compose.yml` file. @@ -49,7 +49,7 @@ If you just want to test Conduit for a short time, you can use the `--rm` flag, ## Docker-compose -If the docker command is not for you or your setup, you can also use one of the provided `docker-compose` files. Depending on your proxy setup, use the `docker-compose.traefik.yml` including `docker-compose.override.traefik.yml` or the normal `docker-compose.yml` for every other reverse proxy. +If the docker command is not for you or your setup, you can also use one of the provided `docker-compose` files. Depending on your proxy setup, use the [`docker-compose.traefik.yml`](docker-compose.traefik.yml) including [`docker-compose.override.traefik.yml`](docker-compose.override.traefik.yml) or the normal [`docker-compose.yml`](../docker-compose.yml) for every other reverse proxy. ### Build diff --git a/docker/docker-compose.traefik.yml b/docker/docker-compose.traefik.yml index ad1dad8..111eaa5 100644 --- a/docker/docker-compose.traefik.yml +++ b/docker/docker-compose.traefik.yml @@ -3,18 +3,19 @@ version: '3' services: homeserver: - ### If you already built the Conduit image with 'docker build', then you can uncomment the - ### 'image' line and comment out the 'build' option. - # image: conduit_homeserver:latest - ### If you want meaningful labels in you built Conduit image, you should run docker-compose like this: + ### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image, + ### then you are ready to go. + image: matrixconduit/matrix-conduit:latest + ### If you want to build a fresh image from the sources, then comment the image line and uncomment the + ### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this: ### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d - build: - context: . - args: - CREATED: - VERSION: - LOCAL: false - GIT_REF: HEAD + # build: + # context: . + # args: + # CREATED: + # VERSION: + # LOCAL: 'false' + # GIT_REF: HEAD restart: unless-stopped volumes: - db:/srv/conduit/.local/share/conduit From 4954df3cc3b2df89a154d1d1272e093930642418 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 25 Aug 2020 13:24:38 +0200 Subject: [PATCH 5/9] feat: handle txn ids --- src/client_server/message.rs | 39 ++++++++++++++++++++++++++---- src/client_server/to_device.rs | 14 +++++++++++ src/database.rs | 8 +++++- src/database/rooms.rs | 3 ++- src/database/rooms/edus.rs | 8 +++++- src/database/transaction_ids.rs | 43 +++++++++++++++++++++++++++++++++ src/pdu.rs | 4 ++- sytest/sytest-whitelist | 1 + 8 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/database/transaction_ids.rs diff --git a/src/client_server/message.rs b/src/client_server/message.rs index d851214..844f44d 100644 --- a/src/client_server/message.rs +++ b/src/client_server/message.rs @@ -1,10 +1,13 @@ use super::State; -use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma}; -use ruma::api::client::{ - error::ErrorKind, - r0::message::{get_message_events, send_message_event}, +use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma}; +use ruma::{ + api::client::{ + error::ErrorKind, + r0::message::{get_message_events, send_message_event}, + }, + EventId, }; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; #[cfg(feature = "conduit_bin")] use rocket::{get, put}; @@ -18,6 +21,29 @@ pub fn send_message_event_route( body: Ruma, ) -> ConduitResult { let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + let device_id = body.device_id.as_ref().expect("user is authenticated"); + + // Check if this is a new transaction id + if let Some(response) = db + .transaction_ids + .existing_txnid(sender_id, device_id, &body.txn_id)? + { + // The client might have sent a txnid of the /sendToDevice endpoint + // This txnid has no response associated with it + if response.is_empty() { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Tried to use txn id already used for an incompatible endpoint.", + )); + } + + let event_id = EventId::try_from( + utils::string_from_bytes(&response) + .map_err(|_| Error::bad_database("Invalid txnid bytes in database."))?, + ) + .map_err(|_| Error::bad_database("Invalid event id in txnid data."))?; + return Ok(send_message_event::Response { event_id }.into()); + } let mut unsigned = serde_json::Map::new(); unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into()); @@ -29,6 +55,7 @@ pub fn send_message_event_route( event_type: body.event_type.clone(), content: serde_json::from_str( body.json_body + .as_ref() .ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))? .get(), ) @@ -41,6 +68,8 @@ pub fn send_message_event_route( &db.account_data, )?; + db.transaction_ids + .add_txnid(sender_id, device_id, &body.txn_id, event_id.as_bytes())?; Ok(send_message_event::Response { event_id }.into()) } diff --git a/src/client_server/to_device.rs b/src/client_server/to_device.rs index ca423fe..8c06d64 100644 --- a/src/client_server/to_device.rs +++ b/src/client_server/to_device.rs @@ -17,6 +17,16 @@ pub fn send_event_to_device_route( body: Ruma, ) -> ConduitResult { let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + let device_id = body.device_id.as_ref().expect("user is authenticated"); + + // Check if this is a new transaction id + if db + .transaction_ids + .existing_txnid(sender_id, device_id, &body.txn_id)? + .is_some() + { + return Ok(send_event_to_device::Response.into()); + } for (target_user_id, map) in &body.messages { for (target_device_id_maybe, event) in map { @@ -52,5 +62,9 @@ pub fn send_event_to_device_route( } } + // Save transaction id with empty data + db.transaction_ids + .add_txnid(sender_id, device_id, &body.txn_id, &[])?; + Ok(send_event_to_device::Response.into()) } diff --git a/src/database.rs b/src/database.rs index 41781b9..b43cc5b 100644 --- a/src/database.rs +++ b/src/database.rs @@ -3,6 +3,7 @@ pub mod globals; pub mod key_backups; pub mod media; pub mod rooms; +pub mod transaction_ids; pub mod uiaa; pub mod users; @@ -23,6 +24,7 @@ pub struct Database { pub account_data: account_data::AccountData, pub media: media::Media, pub key_backups: key_backups::KeyBackups, + pub transaction_ids: transaction_ids::TransactionIds, pub _db: sled::Db, } @@ -90,7 +92,8 @@ impl Database { edus: rooms::RoomEdus { readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?, roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt - roomuserid_lastprivatereadupdate: db.open_tree("roomid_lastprivatereadupdate")?, + roomuserid_lastprivatereadupdate: db + .open_tree("roomid_lastprivatereadupdate")?, typingid_userid: db.open_tree("typingid_userid")?, roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?, presenceid_presence: db.open_tree("presenceid_presence")?, @@ -124,6 +127,9 @@ impl Database { backupid_etag: db.open_tree("backupid_etag")?, backupkeyid_backup: db.open_tree("backupkeyid_backupmetadata")?, }, + transaction_ids: transaction_ids::TransactionIds { + userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?, + }, _db: db, }) } diff --git a/src/database/rooms.rs b/src/database/rooms.rs index bb14c8a..8cfb612 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -621,7 +621,8 @@ impl Rooms { } _ => {} } - self.edus.private_read_set(&room_id, &sender, index, &globals)?; + self.edus + .private_read_set(&room_id, &sender, index, &globals)?; Ok(pdu.event_id) } diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs index fbd3edb..d60e1f1 100644 --- a/src/database/rooms/edus.rs +++ b/src/database/rooms/edus.rs @@ -92,7 +92,13 @@ impl RoomEdus { } /// Sets a private read marker at `count`. - pub fn private_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64, globals: &super::super::globals::Globals) -> Result<()> { + pub fn private_read_set( + &self, + room_id: &RoomId, + user_id: &UserId, + count: u64, + globals: &super::super::globals::Globals, + ) -> Result<()> { let mut key = room_id.to_string().as_bytes().to_vec(); key.push(0xff); key.extend_from_slice(&user_id.to_string().as_bytes()); diff --git a/src/database/transaction_ids.rs b/src/database/transaction_ids.rs new file mode 100644 index 0000000..9485b36 --- /dev/null +++ b/src/database/transaction_ids.rs @@ -0,0 +1,43 @@ +use crate::Result; +use ruma::{DeviceId, UserId}; +use sled::IVec; + +pub struct TransactionIds { + pub(super) userdevicetxnid_response: sled::Tree, // Response can be empty (/sendToDevice) or the event id (/send) +} + +impl TransactionIds { + pub fn add_txnid( + &self, + user_id: &UserId, + device_id: &DeviceId, + txn_id: &str, + data: &[u8], + ) -> Result<()> { + let mut key = user_id.as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(device_id.as_bytes()); + key.push(0xff); + key.extend_from_slice(txn_id.as_bytes()); + + self.userdevicetxnid_response.insert(key, data)?; + + Ok(()) + } + + pub fn existing_txnid( + &self, + user_id: &UserId, + device_id: &DeviceId, + txn_id: &str, + ) -> Result> { + let mut key = user_id.as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(device_id.as_bytes()); + key.push(0xff); + key.extend_from_slice(txn_id.as_bytes()); + + // If there's no entry, this is a new transaction + Ok(self.userdevicetxnid_response.get(key)?) + } +} diff --git a/src/pdu.rs b/src/pdu.rs index 4458423..c948fef 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -71,7 +71,9 @@ impl PduEvent { self.unsigned.insert( "redacted_because".to_owned(), - serde_json::to_string(reason).expect("PduEvent::to_string always works").into() + serde_json::to_string(reason) + .expect("PduEvent::to_string always works") + .into(), ); self.content = new_content.into(); diff --git a/sytest/sytest-whitelist b/sytest/sytest-whitelist index b0b2097..1585233 100644 --- a/sytest/sytest-whitelist +++ b/sytest/sytest-whitelist @@ -38,6 +38,7 @@ Current state appears in timeline in private history with many messages before Deleted tags appear in an incremental v2 /sync Deleting a non-existent alias should return a 404 Device messages wake up /sync +Device messages with the same txn_id are deduplicated Events come down the correct room GET /device/{deviceId} GET /device/{deviceId} gives a 404 for unknown devices From 3f4cb753eef3fb544f71a61144bdd1d9686e5bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Thu, 27 Aug 2020 14:48:20 +0200 Subject: [PATCH 6/9] improvement: add remaining key backup endpoints --- Cargo.lock | 137 +++++++++++++--------------- src/client_server/backup.rs | 174 +++++++++++++++++++++++++++++++++++- src/database/key_backups.rs | 154 +++++++++++++++++++++++++++++++ src/main.rs | 7 ++ 4 files changed, 394 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a7334c..98dbac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ checksum = "4af5687fe33aec5e70ef14caac5e0d363e335e5e5d6385fb75978d0c241b1d67" [[package]] name = "async-trait" -version = "0.1.37" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caae68055714ff28740f310927e04f2eba76ff580b16fb18ed90073ee71646f7" +checksum = "6e1a4a2f97ce50c9d0282c1468816208588441492b40d813b2e0419c22c05e7f" dependencies = [ "proc-macro2", "quote", @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" @@ -148,7 +148,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.4.0", + "miniz_oxide 0.4.1", "object", "rustc-demangle", ] @@ -159,12 +159,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - [[package]] name = "base64" version = "0.12.3" @@ -238,9 +232,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "cc" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" +checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" [[package]] name = "cfg-if" @@ -267,7 +261,7 @@ checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" name = "conduit" version = "0.1.0" dependencies = [ - "base64 0.12.3", + "base64", "directories", "http", "image", @@ -277,7 +271,7 @@ dependencies = [ "reqwest", "rocket", "ruma", - "rust-argon2 0.8.2", + "rust-argon2", "serde", "serde_json", "sled", @@ -298,7 +292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0" dependencies = [ "aes-gcm", - "base64 0.12.3", + "base64", "hkdf", "percent-encoding", "rand", @@ -458,9 +452,9 @@ checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" [[package]] name = "encoding_rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2" dependencies = [ "cfg-if", ] @@ -644,7 +638,7 @@ checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -788,7 +782,7 @@ dependencies = [ "itoa", "pin-project", "socket2", - "time 0.1.43", + "time 0.1.44", "tokio", "tower-service", "tracing", @@ -923,9 +917,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" [[package]] name = "lock_api" @@ -1005,9 +999,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" dependencies = [ "adler", ] @@ -1142,9 +1136,9 @@ checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" [[package]] name = "once_cell" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" [[package]] name = "opaque-debug" @@ -1306,9 +1300,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "proc-macro-crate" @@ -1410,13 +1404,13 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_users" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", "redox_syscall", - "rust-argon2 0.7.0", + "rust-argon2", ] [[package]] @@ -1450,11 +1444,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12427a5577082c24419c9c417db35cfeb65962efc7675bb6b0d5f1f9d315bfe6" +checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e" dependencies = [ - "base64 0.12.3", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1560,7 +1554,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.0.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "ruma-api", "ruma-client-api", @@ -1574,7 +1568,7 @@ dependencies = [ [[package]] name = "ruma-api" version = "0.17.0-alpha.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "http", "percent-encoding", @@ -1589,7 +1583,7 @@ dependencies = [ [[package]] name = "ruma-api-macros" version = "0.17.0-alpha.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1600,7 +1594,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.10.0-alpha.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "assign", "http", @@ -1618,7 +1612,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.2.0" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "js_int", "ruma-identifiers", @@ -1631,7 +1625,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.22.0-alpha.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "js_int", "ruma-common", @@ -1646,7 +1640,7 @@ dependencies = [ [[package]] name = "ruma-events-macros" version = "0.22.0-alpha.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1657,7 +1651,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.0.3" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "js_int", "ruma-api", @@ -1672,7 +1666,7 @@ dependencies = [ [[package]] name = "ruma-identifiers" version = "0.17.4" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "rand", "ruma-identifiers-macros", @@ -1684,7 +1678,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-macros" version = "0.17.4" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "proc-macro2", "quote", @@ -1695,7 +1689,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.1.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "ruma-serde", "serde", @@ -1706,7 +1700,7 @@ dependencies = [ [[package]] name = "ruma-serde" version = "0.2.3" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ "form_urlencoded", "itoa", @@ -1718,33 +1712,21 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.6.0-dev.1" -source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744" +source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0" dependencies = [ - "base64 0.12.3", + "base64", "ring", "serde_json", "untrusted", ] -[[package]] -name = "rust-argon2" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -dependencies = [ - "base64 0.11.0", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "rust-argon2" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" dependencies = [ - "base64 0.12.3", + "base64", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -1767,11 +1749,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac94b333ee2aac3284c5b8a1b7fb4dd11cba88c244e3fe33cdbd047af0eb693" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" dependencies = [ - "base64 0.12.3", + "base64", "log", "ring", "sct", @@ -1970,9 +1952,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "standback" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246" +checksum = "33a71ea1ea5f8747d1af1979bfb7e65c3a025a70609f04ceb78425bc5adad8e6" dependencies = [ "version_check", ] @@ -2061,9 +2043,9 @@ checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "syn" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" dependencies = [ "proc-macro2", "quote", @@ -2106,11 +2088,12 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] @@ -2154,9 +2137,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" [[package]] name = "tokio" @@ -2256,9 +2239,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545" +checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5" dependencies = [ "lazy_static", ] @@ -2369,6 +2352,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.67" diff --git a/src/client_server/backup.rs b/src/client_server/backup.rs index a104964..9994f19 100644 --- a/src/client_server/backup.rs +++ b/src/client_server/backup.rs @@ -3,13 +3,15 @@ use crate::{ConduitResult, Database, Error, Ruma}; use ruma::api::client::{ error::ErrorKind, r0::backup::{ - add_backup_keys, create_backup, get_backup, get_backup_keys, get_latest_backup, - update_backup, + add_backup_key_session, add_backup_key_sessions, add_backup_keys, create_backup, + delete_backup, delete_backup_key_session, delete_backup_key_sessions, delete_backup_keys, + get_backup, get_backup_key_session, get_backup_key_sessions, get_backup_keys, + get_latest_backup, update_backup, }, }; #[cfg(feature = "conduit_bin")] -use rocket::{get, post, put}; +use rocket::{delete, get, post, put}; #[cfg_attr( feature = "conduit_bin", @@ -95,7 +97,22 @@ pub fn get_backup_route( .into()) } -/// Add the received backup_keys to the database. +#[cfg_attr( + feature = "conduit_bin", + delete("/_matrix/client/unstable/room_keys/version/<_>", data = "") +)] +pub fn delete_backup_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + db.key_backups.delete_backup(&sender_id, &body.version)?; + + Ok(delete_backup::Response.into()) +} + +/// Add the received backup keys to the database. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/unstable/room_keys/keys", data = "") @@ -126,6 +143,62 @@ pub fn add_backup_keys_route( .into()) } +/// Add the received backup keys to the database. +#[cfg_attr( + feature = "conduit_bin", + put("/_matrix/client/unstable/room_keys/keys/<_>", data = "") +)] +pub fn add_backup_key_sessions_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + for (session_id, key_data) in &body.sessions { + db.key_backups.add_key( + &sender_id, + &body.version, + &body.room_id, + &session_id, + &key_data, + &db.globals, + )? + } + + Ok(add_backup_key_sessions::Response { + count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(), + etag: db.key_backups.get_etag(sender_id, &body.version)?, + } + .into()) +} + +/// Add the received backup key to the database. +#[cfg_attr( + feature = "conduit_bin", + put("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") +)] +pub fn add_backup_key_session_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + db.key_backups.add_key( + &sender_id, + &body.version, + &body.room_id, + &body.session_id, + &body.session_data, + &db.globals, + )?; + + Ok(add_backup_key_session::Response { + count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(), + etag: db.key_backups.get_etag(sender_id, &body.version)?, + } + .into()) +} + #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/keys", data = "") @@ -140,3 +213,96 @@ pub fn get_backup_keys_route( Ok(get_backup_keys::Response { rooms }.into()) } + +#[cfg_attr( + feature = "conduit_bin", + get("/_matrix/client/unstable/room_keys/keys/<_>", data = "") +)] +pub fn get_backup_key_sessions_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + let sessions = db + .key_backups + .get_room(&sender_id, &body.version, &body.room_id); + + Ok(get_backup_key_sessions::Response { sessions }.into()) +} + +#[cfg_attr( + feature = "conduit_bin", + get("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") +)] +pub fn get_backup_key_session_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + let key_data = + db.key_backups + .get_session(&sender_id, &body.version, &body.room_id, &body.session_id)?; + + Ok(get_backup_key_session::Response { key_data }.into()) +} + +#[cfg_attr( + feature = "conduit_bin", + delete("/_matrix/client/unstable/room_keys/keys", data = "") +)] +pub fn delete_backup_keys_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + db.key_backups.delete_all_keys(&sender_id, &body.version)?; + + Ok(delete_backup_keys::Response { + count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(), + etag: db.key_backups.get_etag(sender_id, &body.version)?, + } + .into()) +} + +#[cfg_attr( + feature = "conduit_bin", + delete("/_matrix/client/unstable/room_keys/keys/<_>", data = "") +)] +pub fn delete_backup_key_sessions_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + db.key_backups + .delete_room_keys(&sender_id, &body.version, &body.room_id)?; + + Ok(delete_backup_key_sessions::Response { + count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(), + etag: db.key_backups.get_etag(sender_id, &body.version)?, + } + .into()) +} + +#[cfg_attr( + feature = "conduit_bin", + delete("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") +)] +pub fn delete_backup_key_session_route( + db: State<'_, Database>, + body: Ruma, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + db.key_backups + .delete_room_key(&sender_id, &body.version, &body.room_id, &body.session_id)?; + + Ok(delete_backup_key_session::Response { + count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(), + etag: db.key_backups.get_etag(sender_id, &body.version)?, + } + .into()) +} diff --git a/src/database/key_backups.rs b/src/database/key_backups.rs index 5b37f1b..1ce7595 100644 --- a/src/database/key_backups.rs +++ b/src/database/key_backups.rs @@ -37,6 +37,28 @@ impl KeyBackups { Ok(version) } + pub fn delete_backup(&self, user_id: &UserId, version: &str) -> Result<()> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&version.as_bytes()); + + self.backupid_algorithm.remove(&key)?; + self.backupid_etag.remove(&key)?; + + key.push(0xff); + + for outdated_key in self + .backupkeyid_backup + .scan_prefix(&key) + .keys() + .filter_map(|r| r.ok()) + { + self.backupkeyid_backup.remove(outdated_key)?; + } + + Ok(()) + } + pub fn update_backup( &self, user_id: &UserId, @@ -163,6 +185,7 @@ impl KeyBackups { let mut prefix = user_id.to_string().as_bytes().to_vec(); prefix.push(0xff); prefix.extend_from_slice(version.as_bytes()); + prefix.push(0xff); let mut rooms = BTreeMap::::new(); @@ -204,4 +227,135 @@ impl KeyBackups { Ok(rooms) } + + pub fn get_room( + &self, + user_id: &UserId, + version: &str, + room_id: &RoomId, + ) -> BTreeMap { + let mut prefix = user_id.to_string().as_bytes().to_vec(); + prefix.push(0xff); + prefix.extend_from_slice(version.as_bytes()); + prefix.push(0xff); + prefix.extend_from_slice(room_id.as_bytes()); + prefix.push(0xff); + + self.backupkeyid_backup + .scan_prefix(&prefix) + .map(|r| { + let (key, value) = r?; + let mut parts = key.rsplit(|&b| b == 0xff); + + let session_id = + utils::string_from_bytes(&parts.next().ok_or_else(|| { + Error::bad_database("backupkeyid_backup key is invalid.") + })?) + .map_err(|_| { + Error::bad_database("backupkeyid_backup session_id is invalid.") + })?; + + let key_data = serde_json::from_slice(&value).map_err(|_| { + Error::bad_database("KeyData in backupkeyid_backup is invalid.") + })?; + + Ok::<_, Error>((session_id, key_data)) + }) + .filter_map(|r| r.ok()) + .collect() + } + + pub fn get_session( + &self, + user_id: &UserId, + version: &str, + room_id: &RoomId, + session_id: &str, + ) -> Result> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(version.as_bytes()); + key.push(0xff); + key.extend_from_slice(room_id.as_bytes()); + key.push(0xff); + key.extend_from_slice(session_id.as_bytes()); + + self.backupkeyid_backup + .get(&key)? + .map(|value| { + serde_json::from_slice(&value) + .map_err(|_| Error::bad_database("KeyData in backupkeyid_backup is invalid.")) + }) + .transpose() + } + + pub fn delete_all_keys(&self, user_id: &UserId, version: &str) -> Result<()> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&version.as_bytes()); + key.push(0xff); + + for outdated_key in self + .backupkeyid_backup + .scan_prefix(&key) + .keys() + .filter_map(|r| r.ok()) + { + self.backupkeyid_backup.remove(outdated_key)?; + } + + Ok(()) + } + + pub fn delete_room_keys( + &self, + user_id: &UserId, + version: &str, + room_id: &RoomId, + ) -> Result<()> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&version.as_bytes()); + key.push(0xff); + key.extend_from_slice(&room_id.as_bytes()); + key.push(0xff); + + for outdated_key in self + .backupkeyid_backup + .scan_prefix(&key) + .keys() + .filter_map(|r| r.ok()) + { + self.backupkeyid_backup.remove(outdated_key)?; + } + + Ok(()) + } + + pub fn delete_room_key( + &self, + user_id: &UserId, + version: &str, + room_id: &RoomId, + session_id: &str, + ) -> Result<()> { + let mut key = user_id.to_string().as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(&version.as_bytes()); + key.push(0xff); + key.extend_from_slice(&room_id.as_bytes()); + key.push(0xff); + key.extend_from_slice(&session_id.as_bytes()); + + for outdated_key in self + .backupkeyid_backup + .scan_prefix(&key) + .keys() + .filter_map(|r| r.ok()) + { + self.backupkeyid_backup.remove(outdated_key)?; + } + + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index bbe7c96..96d0e99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,9 +53,16 @@ fn setup_rocket() -> rocket::Rocket { client_server::claim_keys_route, client_server::create_backup_route, client_server::update_backup_route, + client_server::delete_backup_route, client_server::get_latest_backup_route, client_server::get_backup_route, + client_server::add_backup_key_sessions_route, client_server::add_backup_keys_route, + client_server::delete_backup_key_session_route, + client_server::delete_backup_key_sessions_route, + client_server::delete_backup_keys_route, + client_server::get_backup_key_session_route, + client_server::get_backup_key_sessions_route, client_server::get_backup_keys_route, client_server::set_read_marker_route, client_server::create_typing_event_route, From df55e8ed0b130df3f9197be59196fc7b0590c5f8 Mon Sep 17 00:00:00 2001 From: Faelar Date: Thu, 6 Aug 2020 13:21:53 +0200 Subject: [PATCH 7/9] Add room upgrade. --- src/client_server/room.rs | 199 +++++++++++++++++++++++++++++++++++++- src/database.rs | 1 + src/database/rooms.rs | 128 ++++++++++++++++++++++++ src/main.rs | 1 + sytest/sytest-whitelist | 1 + 5 files changed, 327 insertions(+), 3 deletions(-) diff --git a/src/client_server/room.rs b/src/client_server/room.rs index b5f1529..1b43873 100644 --- a/src/client_server/room.rs +++ b/src/client_server/room.rs @@ -3,15 +3,15 @@ use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma}; use ruma::{ api::client::{ error::ErrorKind, - r0::room::{self, create_room, get_room_event}, + r0::room::{self, create_room, get_room_event, upgrade_room}, }, events::{ room::{guest_access, history_visibility, join_rules, member, name, topic}, EventType, }, - RoomAliasId, RoomId, RoomVersionId, + Raw, RoomAliasId, RoomId, RoomVersionId, }; -use std::{collections::BTreeMap, convert::TryFrom}; +use std::{cmp::max, collections::BTreeMap, convert::TryFrom}; #[cfg(feature = "conduit_bin")] use rocket::{get, post}; @@ -344,3 +344,196 @@ pub fn get_room_event_route( } .into()) } + +#[cfg_attr( + feature = "conduit_bin", + post("/_matrix/client/r0/rooms/<_room_id>/upgrade", data = "") +)] +pub fn upgrade_room_route( + db: State<'_, Database>, + body: Ruma, + _room_id: String, +) -> ConduitResult { + let sender_id = body.sender_id.as_ref().expect("user is authenticated"); + + // Validate the room version requested + let new_version = + RoomVersionId::try_from(body.new_version.clone()).expect("invalid room version id"); + + if !matches!( + new_version, + RoomVersionId::Version5 | RoomVersionId::Version6 + ) { + return Err(Error::BadRequest( + ErrorKind::UnsupportedRoomVersion, + "This server does not support that room version.", + )); + } + + // Create a replacement room + let replacement_room = RoomId::new(db.globals.server_name()); + + // Send a m.room.tombstone event to the old room to indicate that it is not intended to be used any further + // Fail if the sender does not have the required permissions + let tombstone_event_id = db.rooms.append_pdu( + PduBuilder { + room_id: body.room_id.clone(), + sender: sender_id.clone(), + event_type: EventType::RoomTombstone, + content: serde_json::to_value(ruma::events::room::tombstone::TombstoneEventContent { + body: "This room has been replaced".to_string(), + replacement_room: replacement_room.clone(), + }) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: Some("".to_owned()), + redacts: None, + }, + &db.globals, + &db.account_data, + )?; + + // Get the old room federations status + let federate = serde_json::from_value::>( + db.rooms + .room_state_get(&body.room_id, &EventType::RoomCreate, "")? + .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? + .content, + ) + .expect("Raw::from_value always works") + .deserialize() + .map_err(|_| Error::bad_database("Invalid room event in database."))? + .federate; + + // Use the m.room.tombstone event as the predecessor + let predecessor = Some(ruma::events::room::create::PreviousRoom::new( + body.room_id.clone(), + tombstone_event_id, + )); + + // Send a m.room.create event containing a predecessor field and the applicable room_version + let mut create_event_content = + ruma::events::room::create::CreateEventContent::new(sender_id.clone()); + create_event_content.federate = federate; + create_event_content.room_version = new_version; + create_event_content.predecessor = predecessor; + + db.rooms.append_pdu( + PduBuilder { + room_id: replacement_room.clone(), + sender: sender_id.clone(), + event_type: EventType::RoomCreate, + content: serde_json::to_value(create_event_content) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: Some("".to_owned()), + redacts: None, + }, + &db.globals, + &db.account_data, + )?; + + // Join the new room + db.rooms.append_pdu( + PduBuilder { + room_id: replacement_room.clone(), + sender: sender_id.clone(), + event_type: EventType::RoomMember, + content: serde_json::to_value(member::MemberEventContent { + membership: member::MembershipState::Join, + displayname: db.users.displayname(&sender_id)?, + avatar_url: db.users.avatar_url(&sender_id)?, + is_direct: None, + third_party_invite: None, + }) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: Some(sender_id.to_string()), + redacts: None, + }, + &db.globals, + &db.account_data, + )?; + + // Recommended transferable state events list from the specs + let transferable_state_events = vec![ + EventType::RoomServerAcl, + EventType::RoomEncryption, + EventType::RoomName, + EventType::RoomAvatar, + EventType::RoomTopic, + EventType::RoomGuestAccess, + EventType::RoomHistoryVisibility, + EventType::RoomJoinRules, + EventType::RoomPowerLevels, + ]; + + // Replicate transferable state events to the new room + for event_type in transferable_state_events { + let event_content = match db.rooms.room_state_get(&body.room_id, &event_type, "")? { + Some(v) => v.content.clone(), + None => continue, // Skipping missing events. + }; + + db.rooms.append_pdu( + PduBuilder { + room_id: replacement_room.clone(), + sender: sender_id.clone(), + event_type, + content: event_content, + unsigned: None, + state_key: Some("".to_owned()), + redacts: None, + }, + &db.globals, + &db.account_data, + )?; + } + + // Moves any local aliases to the new room + for alias in db.rooms.room_aliases(&body.room_id).filter_map(|r| r.ok()) { + db.rooms + .set_alias(&alias, Some(&replacement_room), &db.globals)?; + } + + // Get the old room power levels + let mut power_levels_event_content = + serde_json::from_value::>( + db.rooms + .room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")? + .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? + .content, + ) + .expect("database contains invalid PDU") + .deserialize() + .map_err(|_| Error::bad_database("Invalid room event in database."))?; + + // Setting events_default and invite to the greater of 50 and users_default + 1 + let new_level = max( + 50.into(), + power_levels_event_content.users_default + 1.into(), + ); + power_levels_event_content.events_default = new_level; + power_levels_event_content.invite = new_level; + + // Modify the power levels in the old room to prevent sending of events and inviting new users + db.rooms + .append_pdu( + PduBuilder { + room_id: body.room_id.clone(), + sender: sender_id.clone(), + event_type: EventType::RoomPowerLevels, + content: serde_json::to_value(power_levels_event_content) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: Some("".to_owned()), + redacts: None, + }, + &db.globals, + &db.account_data, + ) + .ok(); + + // Return the replacement room id + Ok(upgrade_room::Response { replacement_room }.into()) +} diff --git a/src/database.rs b/src/database.rs index b43cc5b..2bb75a5 100644 --- a/src/database.rs +++ b/src/database.rs @@ -112,6 +112,7 @@ impl Database { userroomid_joined: db.open_tree("userroomid_joined")?, roomuserid_joined: db.open_tree("roomuserid_joined")?, + roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?, userroomid_invited: db.open_tree("userroomid_invited")?, roomuserid_invited: db.open_tree("roomuserid_invited")?, userroomid_left: db.open_tree("userroomid_left")?, diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 8cfb612..eee47f3 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -38,6 +38,7 @@ pub struct Rooms { pub(super) userroomid_joined: sled::Tree, pub(super) roomuserid_joined: sled::Tree, + pub(super) roomuseroncejoinedids: sled::Tree, pub(super) userroomid_invited: sled::Tree, pub(super) roomuserid_invited: sled::Tree, pub(super) userroomid_left: sled::Tree, @@ -782,6 +783,104 @@ impl Rooms { match &membership { member::MembershipState::Join => { + // Check if the user never joined this room + if !self.once_joined(&user_id, &room_id)? { + // Add the user ID to the join list then + self.roomuseroncejoinedids.insert(&userroom_id, &[])?; + + // Check if the room has a predecessor + if let Some(predecessor) = serde_json::from_value::< + Raw, + >( + self.room_state_get(&room_id, &EventType::RoomCreate, "")? + .ok_or_else(|| { + Error::bad_database("Found room without m.room.create event.") + })? + .content, + ) + .expect("Raw::from_value always works") + .deserialize() + .map_err(|_| Error::bad_database("Invalid room event in database."))? + .predecessor + { + // Copy user settings from predecessor to the current room: + + // - Push rules + // + // TODO: finish this once push rules are implemented. + // + // let mut push_rules_event_content = account_data + // .get::( + // None, + // user_id, + // EventType::PushRules, + // )?; + // + // NOTE: find where `predecessor.room_id` match + // and update to `room_id`. + // + // account_data + // .update( + // None, + // user_id, + // EventType::PushRules, + // &push_rules_event_content, + // globals, + // ) + // .ok(); + + // - Tags + if let Some(basic_event) = account_data.get::( + Some(&predecessor.room_id), + user_id, + EventType::Tag, + )? { + let tag_event_content = basic_event.content; + + account_data + .update( + Some(room_id), + user_id, + EventType::Tag, + &tag_event_content, + globals, + ) + .ok(); + }; + + // - Direct chat + if let Some(basic_event) = account_data + .get::( + None, + user_id, + EventType::Direct, + )? + { + let mut direct_event_content = basic_event.content; + let mut room_ids_updated = false; + + for room_ids in direct_event_content.0.values_mut() { + if room_ids.iter().any(|r| r == &predecessor.room_id) { + room_ids.push(room_id.clone()); + room_ids_updated = true; + } + } + + if room_ids_updated { + account_data + .update( + None, + user_id, + EventType::Direct, + &direct_event_content, + globals, + ) + .ok(); + } + }; + } + } + self.userroomid_joined.insert(&userroom_id, &[])?; self.roomuserid_joined.insert(&roomuser_id, &[])?; self.userroomid_invited.remove(&userroom_id)?; @@ -1042,6 +1141,27 @@ impl Rooms { }) } + /// Returns an iterator over all User IDs who ever joined a room. + pub fn room_useroncejoined(&self, room_id: &RoomId) -> impl Iterator> { + self.roomuseroncejoinedids + .scan_prefix(room_id.to_string()) + .keys() + .map(|key| { + Ok(UserId::try_from( + utils::string_from_bytes( + &key? + .rsplit(|&b| b == 0xff) + .next() + .expect("rsplit always returns an element"), + ) + .map_err(|_| { + Error::bad_database("User ID in room_useroncejoined is invalid unicode.") + })?, + ) + .map_err(|_| Error::bad_database("User ID in room_useroncejoined is invalid."))?) + }) + } + /// Returns an iterator over all invited members of a room. pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator> { self.roomuserid_invited @@ -1126,6 +1246,14 @@ impl Rooms { }) } + pub fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result { + let mut userroom_id = user_id.to_string().as_bytes().to_vec(); + userroom_id.push(0xff); + userroom_id.extend_from_slice(room_id.to_string().as_bytes()); + + Ok(self.roomuseroncejoinedids.get(userroom_id)?.is_some()) + } + pub fn is_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result { let mut userroom_id = user_id.to_string().as_bytes().to_vec(); userroom_id.push(0xff); diff --git a/src/main.rs b/src/main.rs index 96d0e99..eb060e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,6 +118,7 @@ fn setup_rocket() -> rocket::Rocket { client_server::get_key_changes_route, client_server::get_pushers_route, client_server::set_pushers_route, + client_server::upgrade_room_route, server_server::well_known_server, server_server::get_server_version, server_server::get_server_keys, diff --git a/sytest/sytest-whitelist b/sytest/sytest-whitelist index 1585233..e1f4e5c 100644 --- a/sytest/sytest-whitelist +++ b/sytest/sytest-whitelist @@ -89,6 +89,7 @@ POST /rooms/:room_id/join can join a room POST /rooms/:room_id/leave can leave a room POST /rooms/:room_id/state/m.room.name sets name POST /rooms/:room_id/state/m.room.topic sets topic +POST /rooms/:room_id/upgrade can upgrade a room version POSTed media can be thumbnailed PUT /device/{deviceId} gives a 404 for unknown devices PUT /device/{deviceId} updates device fields From 515465f9004a285cb61ea093ea88ec55cf5bec8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Mon, 31 Aug 2020 13:23:39 +0200 Subject: [PATCH 8/9] fix: make element not show "unknown user" warning The 404 error for /profile in the spec says "There is no profile information for this user or this user does not exist.", but Element assumes every 404 is a user that does not exist. --- src/client_server/profile.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/client_server/profile.rs b/src/client_server/profile.rs index 1313db7..ebcc7eb 100644 --- a/src/client_server/profile.rs +++ b/src/client_server/profile.rs @@ -217,11 +217,8 @@ pub fn get_profile_route( db: State<'_, Database>, body: Ruma, ) -> ConduitResult { - let avatar_url = db.users.avatar_url(&body.user_id)?; - let displayname = db.users.displayname(&body.user_id)?; - - if avatar_url.is_none() && displayname.is_none() { - // Return 404 if we don't have a profile for this id + if !db.users.exists(&body.user_id)? { + // Return 404 if this user doesn't exist return Err(Error::BadRequest( ErrorKind::NotFound, "Profile was not found.", @@ -229,8 +226,8 @@ pub fn get_profile_route( } Ok(get_profile::Response { - avatar_url, - displayname, + avatar_url: db.users.avatar_url(&body.user_id)?, + displayname: db.users.displayname(&body.user_id)?, } .into()) } From 698e44a73237c6db520786c6248df20543aae47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 1 Sep 2020 13:07:32 +0200 Subject: [PATCH 9/9] Fix /upgrade account data problems --- src/database/rooms.rs | 47 ++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/database/rooms.rs b/src/database/rooms.rs index eee47f3..22e61e6 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -804,7 +804,6 @@ impl Rooms { .predecessor { // Copy user settings from predecessor to the current room: - // - Push rules // // TODO: finish this once push rules are implemented. @@ -829,37 +828,27 @@ impl Rooms { // ) // .ok(); - // - Tags - if let Some(basic_event) = account_data.get::( + // Copy old tags to new room + if let Some(tag_event) = account_data.get::( Some(&predecessor.room_id), user_id, EventType::Tag, )? { - let tag_event_content = basic_event.content; - account_data - .update( - Some(room_id), - user_id, - EventType::Tag, - &tag_event_content, - globals, - ) + .update(Some(room_id), user_id, EventType::Tag, &tag_event, globals) .ok(); }; - // - Direct chat - if let Some(basic_event) = account_data + // Copy direct chat flag + if let Some(mut direct_event) = account_data .get::( - None, - user_id, - EventType::Direct, - )? - { - let mut direct_event_content = basic_event.content; + None, + user_id, + EventType::Direct, + )? { let mut room_ids_updated = false; - for room_ids in direct_event_content.0.values_mut() { + for room_ids in direct_event.content.0.values_mut() { if room_ids.iter().any(|r| r == &predecessor.room_id) { room_ids.push(room_id.clone()); room_ids_updated = true; @@ -867,15 +856,13 @@ impl Rooms { } if room_ids_updated { - account_data - .update( - None, - user_id, - EventType::Direct, - &direct_event_content, - globals, - ) - .ok(); + account_data.update( + None, + user_id, + EventType::Direct, + &direct_event, + globals, + )?; } }; }