diff --git a/.forgejo/actions/rust-toolchain/action.yml b/.forgejo/actions/rust-toolchain/action.yml index 71fb96f5..ae5cfcee 100644 --- a/.forgejo/actions/rust-toolchain/action.yml +++ b/.forgejo/actions/rust-toolchain/action.yml @@ -19,11 +19,20 @@ outputs: rustc_version: description: The rustc version installed value: ${{ steps.rustc-version.outputs.version }} + rustup_version: + description: The rustup version installed + value: ${{ steps.rustup-version.outputs.version }} runs: using: composite steps: + - name: Check if rustup is already installed + shell: bash + id: rustup-version + run: | + echo "version=$(rustup --version)" >> $GITHUB_OUTPUT - name: Cache rustup toolchains + if: steps.rustup-version.outputs.version == '' uses: actions/cache@v3 with: path: | @@ -33,6 +42,7 @@ runs: # Requires repo to be cloned if toolchain is not specified key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }} - name: Install Rust toolchain + if: steps.rustup-version.outputs.version == '' shell: bash run: | if ! command -v rustup &> /dev/null ; then diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index ec466c58..ec05fb45 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -57,7 +57,6 @@ jobs: build-image: runs-on: dind - container: ghcr.io/catthehacker/ubuntu:act-latest needs: define-variables permissions: contents: read @@ -211,7 +210,6 @@ jobs: merge: runs-on: dind - container: ghcr.io/catthehacker/ubuntu:act-latest needs: [define-variables, build-image] steps: - name: Download digests diff --git a/.typos.toml b/.typos.toml index 41c81085..73a5d6ea 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,5 +1,5 @@ [files] -extend-exclude = ["*.csr"] +extend-exclude = ["*.csr", "*.lock"] [default.extend-words] "allocatedp" = "allocatedp" diff --git a/Cargo.lock b/Cargo.lock index 160be0c7..3e80849c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3695,7 +3695,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "assign", "js_int", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3727,7 +3727,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "assign", @@ -3750,7 +3750,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "base64 0.22.1", @@ -3782,7 +3782,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "as_variant", "indexmap 2.9.0", @@ -3807,7 +3807,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "bytes", "headers", @@ -3829,7 +3829,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "thiserror 2.0.12", @@ -3838,7 +3838,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3848,7 +3848,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "cfg-if", "proc-macro-crate", @@ -3863,7 +3863,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "js_int", "ruma-common", @@ -3875,7 +3875,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a48665b682be1016cea53ea5e7787442dfe7c1de#a48665b682be1016cea53ea5e7787442dfe7c1de" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 1abff107..80aacf39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -350,7 +350,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "d6870a7fb7f6cccff63f7fd0ff6c581bad80e983" +rev = "a48665b682be1016cea53ea5e7787442dfe7c1de" features = [ "compat", "rand", @@ -381,7 +381,7 @@ features = [ "unstable-msc4121", "unstable-msc4125", "unstable-msc4186", - "unstable-msc4203", # sending to-device events to appservices + "unstable-msc4203", # sending to-device events to appservices "unstable-msc4210", # remove legacy mentions "unstable-extensible-events", "unstable-pdu", diff --git a/src/api/client/membership.rs b/src/api/client/membership.rs index 2847d668..e587d806 100644 --- a/src/api/client/membership.rs +++ b/src/api/client/membership.rs @@ -2162,6 +2162,109 @@ async fn knock_room_by_id_helper( } } + // For knock_restricted rooms, check if the user meets the restricted conditions + // If they do, attempt to join instead of knock + // This is not mentioned in the spec, but should be allowable (we're allowed to + // auto-join invites to knocked rooms) + let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await; + if let JoinRule::KnockRestricted(restricted) = &join_rule { + let restriction_rooms: Vec<_> = restricted + .allow + .iter() + .filter_map(|a| match a { + | AllowRule::RoomMembership(r) => Some(&r.room_id), + | _ => None, + }) + .collect(); + + // Check if the user is in any of the allowed rooms + let mut user_meets_restrictions = false; + for restriction_room_id in &restriction_rooms { + if services + .rooms + .state_cache + .is_joined(sender_user, restriction_room_id) + .await + { + user_meets_restrictions = true; + break; + } + } + + // If the user meets the restrictions, try joining instead + if user_meets_restrictions { + debug_info!( + "{sender_user} meets the restricted criteria in knock_restricted room \ + {room_id}, attempting to join instead of knock" + ); + // For this case, we need to drop the state lock and get a new one in + // join_room_by_id_helper We need to release the lock here and let + // join_room_by_id_helper acquire it again + drop(state_lock); + match join_room_by_id_helper( + services, + sender_user, + room_id, + reason.clone(), + servers, + None, + &None, + ) + .await + { + | Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())), + | Err(e) => { + debug_warn!( + "Failed to convert knock to join for {sender_user} in {room_id}: {e:?}" + ); + // Get a new state lock for the remaining knock logic + let new_state_lock = services.rooms.state.mutex.lock(room_id).await; + + let server_in_room = services + .rooms + .state_cache + .server_in_room(services.globals.server_name(), room_id) + .await; + + let local_knock = server_in_room + || servers.is_empty() + || (servers.len() == 1 && services.globals.server_is_ours(&servers[0])); + + if local_knock { + knock_room_helper_local( + services, + sender_user, + room_id, + reason, + servers, + new_state_lock, + ) + .boxed() + .await?; + } else { + knock_room_helper_remote( + services, + sender_user, + room_id, + reason, + servers, + new_state_lock, + ) + .boxed() + .await?; + } + + return Ok(knock_room::v3::Response::new(room_id.to_owned())); + }, + } + } + } else if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) { + debug_warn!( + "{sender_user} attempted to knock on room {room_id} but its join rule is \ + {join_rule:?}, not knock or knock_restricted" + ); + } + let server_in_room = services .rooms .state_cache @@ -2209,6 +2312,12 @@ async fn knock_room_helper_local( return Err!(Request(Forbidden("This room does not support knocking."))); } + // Verify that this room has a valid knock or knock_restricted join rule + let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await; + if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) { + return Err!(Request(Forbidden("This room's join rule does not allow knocking."))); + } + let content = RoomMemberEventContent { displayname: services.users.displayname(sender_user).await.ok(), avatar_url: services.users.avatar_url(sender_user).await.ok(), diff --git a/src/api/client/sync/v3.rs b/src/api/client/sync/v3.rs index 8eac6b66..41b3d17f 100644 --- a/src/api/client/sync/v3.rs +++ b/src/api/client/sync/v3.rs @@ -808,7 +808,7 @@ async fn load_joined_room( let typings = services .rooms .typing - .typings_all(room_id, sender_user) + .typings_event_for_user(room_id, sender_user) .await?; Ok(vec![serde_json::from_str(&serde_json::to_string(&typings)?)?]) diff --git a/src/api/client/sync/v5.rs b/src/api/client/sync/v5.rs index f3fc0f44..65a820a0 100644 --- a/src/api/client/sync/v5.rs +++ b/src/api/client/sync/v5.rs @@ -33,6 +33,7 @@ use ruma::{ events::{ AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType, room::member::{MembershipState, RoomMemberEventContent}, + typing::TypingEventContent, }, serde::Raw, uint, @@ -205,6 +206,9 @@ pub(crate) async fn sync_events_v5_route( _ = tokio::time::timeout(duration, watcher).await; } + let typing = collect_typing_events(services, sender_user, &body, &todo_rooms).await?; + response.extensions.typing = typing; + trace!( rooms = ?response.rooms.len(), account_data = ?response.extensions.account_data.rooms.len(), @@ -288,6 +292,8 @@ where Rooms: Iterator + Clone + Send + 'a, AllRooms: Iterator + Clone + Send + 'a, { + // TODO MSC4186: Implement remaining list filters: is_dm, is_encrypted, + // room_types. for (list_id, list) in &body.lists { let active_rooms: Vec<_> = match list.filters.as_ref().and_then(|f| f.is_invite) { | None => all_rooms.clone().collect(), @@ -665,6 +671,62 @@ where } Ok(rooms) } + +async fn collect_typing_events( + services: &Services, + sender_user: &UserId, + body: &sync_events::v5::Request, + todo_rooms: &TodoRooms, +) -> Result { + if !body.extensions.typing.enabled.unwrap_or(false) { + return Ok(sync_events::v5::response::Typing::default()); + } + let rooms: Vec<_> = body.extensions.typing.rooms.clone().unwrap_or_else(|| { + body.room_subscriptions + .keys() + .map(ToOwned::to_owned) + .collect() + }); + let lists: Vec<_> = body + .extensions + .typing + .lists + .clone() + .unwrap_or_else(|| body.lists.keys().map(ToOwned::to_owned).collect::>()); + + if rooms.is_empty() && lists.is_empty() { + return Ok(sync_events::v5::response::Typing::default()); + } + + let mut typing_response = sync_events::v5::response::Typing::default(); + for (room_id, (required_state_request, timeline_limit, roomsince)) in todo_rooms { + if services.rooms.typing.last_typing_update(room_id).await? <= *roomsince { + continue; + } + + match services + .rooms + .typing + .typing_users_for_user(room_id, sender_user) + .await + { + | Ok(typing_users) => { + typing_response.rooms.insert( + room_id.to_owned(), // Already OwnedRoomId + Raw::new(&sync_events::v5::response::SyncTypingEvent { + content: TypingEventContent::new(typing_users), + })?, + ); + }, + | Err(e) => { + warn!(%room_id, "Failed to get typing events for room: {}", e); + }, + } + } + + Ok(typing_response) +} + async fn collect_account_data( services: &Services, (sender_user, _, globalsince, body): (&UserId, &DeviceId, u64, &sync_events::v5::Request), diff --git a/src/build_metadata/build.rs b/src/build_metadata/build.rs index bfdf20b1..bf84d508 100644 --- a/src/build_metadata/build.rs +++ b/src/build_metadata/build.rs @@ -79,12 +79,12 @@ fn main() { // --- Rerun Triggers --- // TODO: The git rerun triggers seem to always run - // Rerun if the git HEAD changes - println!("cargo:rerun-if-changed=.git/HEAD"); - // Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch) - if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"]) { - println!("cargo:rerun-if-changed=.git/{ref_path}"); - } + // // Rerun if the git HEAD changes + // println!("cargo:rerun-if-changed=.git/HEAD"); + // // Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch) + // if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"]) + // { println!("cargo:rerun-if-changed=.git/{ref_path}"); + // } println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH"); println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH_SHORT"); diff --git a/src/core/matrix/state_res/event_auth.rs b/src/core/matrix/state_res/event_auth.rs index 715e5156..759ab5cb 100644 --- a/src/core/matrix/state_res/event_auth.rs +++ b/src/core/matrix/state_res/event_auth.rs @@ -638,7 +638,7 @@ fn valid_membership_change( warn!(?target_user_membership_event_id, "Banned user can't join"); false } else if (join_rules == JoinRule::Invite - || room_version.allow_knocking && join_rules == JoinRule::Knock) + || room_version.allow_knocking && (join_rules == JoinRule::Knock || matches!(join_rules, JoinRule::KnockRestricted(_)))) // If the join_rule is invite then allow if membership state is invite or join && (target_user_current_membership == MembershipState::Join || target_user_current_membership == MembershipState::Invite) diff --git a/src/core/mod.rs b/src/core/mod.rs index b91cdf0b..aaacd4d8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -21,7 +21,10 @@ pub use ::toml; pub use ::tracing; pub use config::Config; pub use error::Error; -pub use info::{rustc_flags_capture, version, version::version}; +pub use info::{ + rustc_flags_capture, version, + version::{name, version}, +}; pub use matrix::{Event, EventTypeExt, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res}; pub use server::Server; pub use utils::{ctor, dtor, implement, result, result::Result}; diff --git a/src/main/clap.rs b/src/main/clap.rs index 9b63af19..a3b2b19a 100644 --- a/src/main/clap.rs +++ b/src/main/clap.rs @@ -15,7 +15,7 @@ use conduwuit_core::{ #[clap( about, long_about = None, - name = "conduwuit", + name = conduwuit_core::name(), version = conduwuit_core::version(), )] pub(crate) struct Args { diff --git a/src/service/rooms/typing/mod.rs b/src/service/rooms/typing/mod.rs index a81ee95c..28b9dfa7 100644 --- a/src/service/rooms/typing/mod.rs +++ b/src/service/rooms/typing/mod.rs @@ -179,18 +179,15 @@ impl Service { .unwrap_or(0)) } - /// Returns a new typing EDU. - pub async fn typings_all( + pub async fn typing_users_for_user( &self, room_id: &RoomId, sender_user: &UserId, - ) -> Result> { + ) -> Result> { let room_typing_indicators = self.typing.read().await.get(room_id).cloned(); let Some(typing_indicators) = room_typing_indicators else { - return Ok(SyncEphemeralRoomEvent { - content: ruma::events::typing::TypingEventContent { user_ids: Vec::new() }, - }); + return Ok(Vec::new()); }; let user_ids: Vec<_> = typing_indicators @@ -207,8 +204,19 @@ impl Service { .collect() .await; + Ok(user_ids) + } + + /// Returns a new typing EDU. + pub async fn typings_event_for_user( + &self, + room_id: &RoomId, + sender_user: &UserId, + ) -> Result> { Ok(SyncEphemeralRoomEvent { - content: ruma::events::typing::TypingEventContent { user_ids }, + content: ruma::events::typing::TypingEventContent { + user_ids: self.typing_users_for_user(room_id, sender_user).await?, + }, }) }