mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-07-05 06:21:56 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 446db274a3 | |||
| 6840ec45f7 | |||
| c4a2773230 | |||
| 502fbbf0cd | |||
| 19bd8a3c05 | |||
| 8ae73d455f | |||
| ccb112ef05 | |||
| b00f6ffbed | |||
| 2e252f0841 | |||
| 936f0a669b | |||
| 35b7b45ea0 | |||
| ff92573103 | |||
| 4ed19a1630 | |||
| a35f009d41 | |||
| 540cd28d44 | |||
| 344e1e7d76 | |||
| 4446e96889 | |||
| edb92f021b | |||
| 40ebe37992 | |||
| a27659c73f | |||
| fa460fe97c | |||
| c2620ba57b | |||
| 4024349424 | |||
| 240088c1f5 | |||
| 91229ac3bf | |||
| 854e5f7199 | |||
| 96a58f6d69 |
@@ -1,107 +0,0 @@
|
||||
name: Update flake hashes
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- "Cargo.lock"
|
||||
- "Cargo.toml"
|
||||
- "rust-toolchain.toml"
|
||||
|
||||
jobs:
|
||||
update-flake-hashes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
fetch-single-branch: true
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
|
||||
- uses: https://github.com/cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.1.6
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
# We can skip getting a toolchain hash if this was ran as a dispatch with the intent
|
||||
# to update just the rocksdb hash. If this was ran as a dispatch and the toolchain
|
||||
# files are changed, we still update them, as well as the rocksdb import.
|
||||
- name: Detect changed files
|
||||
id: changes
|
||||
run: |
|
||||
git fetch origin ${{ forgejo.base_ref }} --depth=1 || true
|
||||
if [ -n "${{ forgejo.event.pull_request.base.sha }}" ]; then
|
||||
base=${{ forgejo.event.pull_request.base.sha }}
|
||||
else
|
||||
base=$(git rev-parse HEAD~1)
|
||||
fi
|
||||
echo "Base: $base"
|
||||
echo "HEAD: $(git rev-parse HEAD)"
|
||||
git diff --name-only $base HEAD > changed_files.txt
|
||||
echo "files=$(cat changed_files.txt)" >> $FORGEJO_OUTPUT
|
||||
|
||||
- name: Get new toolchain hash
|
||||
if: contains(steps.changes.outputs.files, 'Cargo.toml') || contains(steps.changes.outputs.files, 'Cargo.lock') || contains(steps.changes.outputs.files, 'rust-toolchain.toml')
|
||||
run: |
|
||||
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
|
||||
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = pkgsHost.lib.fakeSha256;"); found=0} 1' flake.nix > temp.nix && mv temp.nix flake.nix
|
||||
|
||||
# Build continuwuity and filter for the new hash
|
||||
# We do `|| true` because we want this to fail without stopping the workflow
|
||||
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_toolchain_hash.txt) || true
|
||||
|
||||
# Place the new hash in place of the empty hash
|
||||
new_hash=$(cat new_toolchain_hash.txt)
|
||||
sed -i "s|pkgsHost.lib.fakeSha256|\"$new_hash\"|" flake.nix
|
||||
|
||||
echo "New hash:"
|
||||
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' flake.nix
|
||||
echo "Expected new hash:"
|
||||
cat new_toolchain_hash.txt
|
||||
|
||||
rm new_toolchain_hash.txt
|
||||
|
||||
- name: Get new rocksdb hash
|
||||
run: |
|
||||
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
|
||||
awk '/repo = "rocksdb";/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = pkgsHost.lib.fakeSha256;"); found=0} 1' flake.nix > temp.nix && mv temp.nix flake.nix
|
||||
|
||||
# Build continuwuity and filter for the new hash
|
||||
# We do `|| true` because we want this to fail without stopping the workflow
|
||||
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_rocksdb_hash.txt) || true
|
||||
|
||||
# Place the new hash in place of the empty hash
|
||||
new_hash=$(cat new_rocksdb_hash.txt)
|
||||
sed -i "s|pkgsHost.lib.fakeSha256|\"$new_hash\"|" flake.nix
|
||||
|
||||
echo "New hash:"
|
||||
awk -F'"' '/repo = "rocksdb";/{found=1; next} found && /sha256 =/{print $2; found=0}' flake.nix
|
||||
echo "Expected new hash:"
|
||||
cat new_rocksdb_hash.txt
|
||||
|
||||
rm new_rocksdb_hash.txt
|
||||
|
||||
- name: Show diff
|
||||
run: git diff flake.nix
|
||||
|
||||
- name: Push changes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --quiet --exit-code; then
|
||||
echo "No changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.email "renovate@mail.ellis.link"
|
||||
git config user.name "renovate"
|
||||
|
||||
REF="${{ forgejo.head_ref }}"
|
||||
|
||||
git fetch origin "$REF"
|
||||
git checkout "$REF"
|
||||
|
||||
git commit -a -m "chore(Nix): Updated flake hashes"
|
||||
|
||||
git push origin HEAD:refs/heads/"$REF"
|
||||
@@ -381,7 +381,6 @@ features = [
|
||||
"unstable-msc4095",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4210", # remove legacy mentions
|
||||
|
||||
@@ -4,14 +4,11 @@
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, join};
|
||||
use ruma::{
|
||||
OwnedServerName, RoomId, UserId,
|
||||
api::{client::membership::invite_user, federation::membership::create_invite},
|
||||
events::{
|
||||
invite_permission_config::FilterLevel,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use service::Services;
|
||||
|
||||
@@ -50,21 +47,22 @@ pub(crate) async fn invite_user_route(
|
||||
.await?;
|
||||
|
||||
match &body.recipient {
|
||||
| invite_user::v3::InvitationRecipient::UserId { user_id: recipient_user } => {
|
||||
let sender_filter_level = services
|
||||
.users
|
||||
.invite_filter_level(recipient_user, sender_user)
|
||||
.await;
|
||||
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
|
||||
let sender_ignored_recipient = services.users.user_is_ignored(sender_user, user_id);
|
||||
let recipient_ignored_by_sender =
|
||||
services.users.user_is_ignored(user_id, sender_user);
|
||||
|
||||
if !matches!(sender_filter_level, FilterLevel::Allow) {
|
||||
// drop invites if the sender has the recipient filtered
|
||||
let (sender_ignored_recipient, recipient_ignored_by_sender) =
|
||||
join!(sender_ignored_recipient, recipient_ignored_by_sender);
|
||||
|
||||
if sender_ignored_recipient {
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
if let Ok(target_user_membership) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, recipient_user)
|
||||
.get_member(&body.room_id, user_id)
|
||||
.await
|
||||
{
|
||||
if target_user_membership.membership == MembershipState::Ban {
|
||||
@@ -72,27 +70,16 @@ pub(crate) async fn invite_user_route(
|
||||
}
|
||||
}
|
||||
|
||||
// check for blocked invites if the recipient is a local user.
|
||||
if services.globals.user_is_local(recipient_user) {
|
||||
let recipient_filter_level = services
|
||||
.users
|
||||
.invite_filter_level(sender_user, recipient_user)
|
||||
.await;
|
||||
|
||||
// ignored invites aren't handled here
|
||||
// since the recipient's membership should still be changed to `invite`.
|
||||
// they're filtered out in the individual /sync handlers.
|
||||
if matches!(recipient_filter_level, FilterLevel::Block) {
|
||||
return Err!(Request(InviteBlocked(
|
||||
"{recipient_user} has blocked invites from you."
|
||||
)));
|
||||
}
|
||||
if recipient_ignored_by_sender {
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
return Ok(invite_user::v3::Response {});
|
||||
}
|
||||
|
||||
invite_helper(
|
||||
&services,
|
||||
sender_user,
|
||||
recipient_user,
|
||||
user_id,
|
||||
&body.room_id,
|
||||
body.reason.clone(),
|
||||
false,
|
||||
@@ -111,7 +98,7 @@ pub(crate) async fn invite_user_route(
|
||||
pub(crate) async fn invite_helper(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
recipient_user: &UserId,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
is_direct: bool,
|
||||
@@ -124,12 +111,12 @@ pub(crate) async fn invite_helper(
|
||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
|
||||
if !services.globals.user_is_local(recipient_user) {
|
||||
if !services.globals.user_is_local(user_id) {
|
||||
let (pdu, pdu_json, invite_room_state) = {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(user_id).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
@@ -139,7 +126,7 @@ pub(crate) async fn invite_helper(
|
||||
.rooms
|
||||
.timeline
|
||||
.create_hash_and_sign_event(
|
||||
PduBuilder::state(recipient_user.to_string(), &content),
|
||||
PduBuilder::state(user_id.to_string(), &content),
|
||||
sender_user,
|
||||
Some(room_id),
|
||||
&state_lock,
|
||||
@@ -157,7 +144,7 @@ pub(crate) async fn invite_helper(
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(recipient_user.server_name(), create_invite::v2::Request {
|
||||
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: (*pdu.event_id).to_owned(),
|
||||
room_version: room_version_id.clone(),
|
||||
@@ -186,7 +173,7 @@ pub(crate) async fn invite_helper(
|
||||
return Err!(Request(BadJson(warn!(
|
||||
%pdu.event_id, %event_id,
|
||||
"Server {} sent event with wrong event ID",
|
||||
recipient_user.server_name()
|
||||
user_id.server_name()
|
||||
))));
|
||||
}
|
||||
|
||||
@@ -226,9 +213,9 @@ pub(crate) async fn invite_helper(
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(recipient_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
|
||||
blurhash: services.users.blurhash(recipient_user).await.ok(),
|
||||
displayname: services.users.displayname(user_id).await.ok(),
|
||||
avatar_url: services.users.avatar_url(user_id).await.ok(),
|
||||
blurhash: services.users.blurhash(user_id).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
@@ -238,7 +225,7 @@ pub(crate) async fn invite_helper(
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(recipient_user.to_string(), &content),
|
||||
PduBuilder::state(user_id.to_string(), &content),
|
||||
sender_user,
|
||||
Some(room_id),
|
||||
&state_lock,
|
||||
|
||||
+11
-43
@@ -30,7 +30,6 @@
|
||||
events::{
|
||||
AnyStateEvent, StateEventType,
|
||||
TimelineEventType::{self, *},
|
||||
invite_permission_config::FilterLevel,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
@@ -268,7 +267,7 @@ pub(crate) async fn ignored_filter(
|
||||
pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||
services: &Services,
|
||||
event: &Pdu,
|
||||
recipient_user: &UserId,
|
||||
user_id: &UserId,
|
||||
) -> bool
|
||||
where
|
||||
Pdu: Event + Send + Sync,
|
||||
@@ -279,28 +278,20 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||
return true;
|
||||
}
|
||||
|
||||
if IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok() {
|
||||
// this PDU is a non-state event which it is safe to ignore
|
||||
return true;
|
||||
}
|
||||
let ignored_type = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
|
||||
|
||||
let sender_user = event.sender();
|
||||
|
||||
if services
|
||||
let ignored_server = services
|
||||
.moderation
|
||||
.is_remote_server_ignored(sender_user.server_name())
|
||||
{
|
||||
// this PDU was sent by a remote server which we are ignoring
|
||||
return true;
|
||||
}
|
||||
.is_remote_server_ignored(event.sender().server_name());
|
||||
|
||||
if services
|
||||
.users
|
||||
.user_is_ignored(sender_user, recipient_user)
|
||||
.await && !services.config.send_messages_from_ignored_users_to_client
|
||||
if ignored_type
|
||||
&& (ignored_server
|
||||
|| (!services.config.send_messages_from_ignored_users_to_client
|
||||
&& services
|
||||
.users
|
||||
.user_is_ignored(event.sender(), user_id)
|
||||
.await))
|
||||
{
|
||||
// the recipient of this PDU has the sender ignored, and we're not
|
||||
// configured to send ignored messages to clients
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -329,29 +320,6 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
|
||||
filter.matches(pdu).then_some(item)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) async fn is_ignored_invite(
|
||||
services: &Services,
|
||||
recipient_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> bool {
|
||||
let Ok(sender_user) = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_sender(recipient_user, room_id)
|
||||
.await
|
||||
else {
|
||||
// the invite may have been sent before the invite_sender table existed.
|
||||
// assume it's not ignored
|
||||
return false;
|
||||
};
|
||||
|
||||
services
|
||||
.users
|
||||
.invite_filter_level(&sender_user, recipient_user)
|
||||
.await == FilterLevel::Ignore
|
||||
}
|
||||
|
||||
#[cfg_attr(debug_assertions, ctor::ctor)]
|
||||
fn _is_sorted() {
|
||||
debug_assert!(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
@@ -13,7 +13,6 @@
|
||||
api::client::room::{self, create_room},
|
||||
events::{
|
||||
TimelineEventType,
|
||||
invite_permission_config::FilterLevel,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
@@ -122,40 +121,6 @@ pub(crate) async fn create_room_route(
|
||||
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
|
||||
}
|
||||
|
||||
let mut invitees = BTreeSet::new();
|
||||
|
||||
for recipient_user in &body.invite {
|
||||
if !matches!(
|
||||
services
|
||||
.users
|
||||
.invite_filter_level(recipient_user, sender_user)
|
||||
.await,
|
||||
FilterLevel::Allow
|
||||
) {
|
||||
// drop invites if the creator has them blocked
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the recipient of the invite is local and has the sender blocked, error
|
||||
// out. if the recipient is remote we can't tell yet, and if they're local and
|
||||
// have the sender _ignored_ their invite will be filtered out in
|
||||
// the handlers for the individual /sync endpoints
|
||||
if services.globals.user_is_local(recipient_user)
|
||||
&& matches!(
|
||||
services
|
||||
.users
|
||||
.invite_filter_level(sender_user, recipient_user)
|
||||
.await,
|
||||
FilterLevel::Block
|
||||
) {
|
||||
return Err!(Request(InviteBlocked(
|
||||
"{recipient_user} has blocked invites from you."
|
||||
)));
|
||||
}
|
||||
|
||||
invitees.insert(recipient_user.clone());
|
||||
}
|
||||
|
||||
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
|
||||
| Some(alias) =>
|
||||
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
|
||||
@@ -287,11 +252,19 @@ pub(crate) async fn create_room_route(
|
||||
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
let mut power_levels_to_grant = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
|
||||
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
|
||||
|
||||
if preset == RoomPreset::TrustedPrivateChat {
|
||||
for recipient_user in &invitees {
|
||||
power_levels_to_grant.insert(recipient_user.clone(), int!(100));
|
||||
for invite in &body.invite {
|
||||
if services.users.user_is_ignored(sender_user, invite).await {
|
||||
continue;
|
||||
} else if services.users.user_is_ignored(invite, sender_user).await {
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
continue;
|
||||
}
|
||||
|
||||
users.insert(invite.clone(), int!(100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,33 +289,7 @@ pub(crate) async fn create_room_route(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
power_levels_to_grant.insert(sender_user.to_owned(), int!(100));
|
||||
creators.clear(); // If this vec is not empty, default_power_levels_content will
|
||||
// treat this as a v12 room
|
||||
}
|
||||
|
||||
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
|
||||
// Do we care about additional_creators?
|
||||
if room_features.explicitly_privilege_room_creators {
|
||||
// Have they been specified?
|
||||
if let Some(additional_creators) = create_content.get("additional_creators") {
|
||||
// Are they a real array?
|
||||
if let Some(additional_creators) = additional_creators.as_array() {
|
||||
// Iterate through them
|
||||
for creator in additional_creators {
|
||||
// Are they a string?
|
||||
if let Some(creator) = creator.as_str() {
|
||||
// Do they parse into a real user ID?
|
||||
if let Ok(creator) = OwnedUserId::parse(creator) {
|
||||
// Add them to the power levels and creators
|
||||
creators.push(creator.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
power_levels_to_grant.insert(sender_user.to_owned(), int!(100));
|
||||
users.insert(sender_user.to_owned(), int!(100));
|
||||
creators.clear(); // If this vec is not empty, default_power_levels_content will
|
||||
// treat this as a v12 room
|
||||
}
|
||||
@@ -350,7 +297,7 @@ pub(crate) async fn create_room_route(
|
||||
let power_levels_content = default_power_levels_content(
|
||||
body.power_level_content_override.as_ref(),
|
||||
&body.visibility,
|
||||
power_levels_to_grant,
|
||||
users,
|
||||
creators,
|
||||
)?;
|
||||
|
||||
@@ -512,9 +459,17 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
drop(state_lock);
|
||||
for recipient_user in &invitees {
|
||||
for user_id in &body.invite {
|
||||
if services.users.user_is_ignored(sender_user, user_id).await {
|
||||
continue;
|
||||
} else if services.users.user_is_ignored(user_id, sender_user).await {
|
||||
// silently drop the invite to the recipient if they've been ignored by the
|
||||
// sender, pretend it worked
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
invite_helper(&services, sender_user, recipient_user, &room_id, None, body.is_direct)
|
||||
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -60,10 +60,7 @@
|
||||
use service::rooms::short::{ShortEventId, ShortStateKey};
|
||||
|
||||
use super::{load_timeline, share_encrypted_room};
|
||||
use crate::{
|
||||
Ruma, RumaResponse,
|
||||
client::{ignored_filter, is_ignored_invite},
|
||||
};
|
||||
use crate::{Ruma, RumaResponse, client::ignored_filter};
|
||||
|
||||
#[derive(Default)]
|
||||
struct StateChanges {
|
||||
@@ -241,13 +238,6 @@ pub(crate) async fn build_sync_events(
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(sender_user)
|
||||
.wide_filter_map(async |(room_id, invite_state)| {
|
||||
if is_ignored_invite(services, sender_user, &room_id).await {
|
||||
None
|
||||
} else {
|
||||
Some((room_id, invite_state))
|
||||
}
|
||||
})
|
||||
.fold_default(|mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| async move {
|
||||
let invite_count = services
|
||||
.rooms
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
utils::{
|
||||
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
||||
math::{ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
|
||||
stream::WidebandExt,
|
||||
},
|
||||
warn,
|
||||
};
|
||||
@@ -40,7 +39,7 @@
|
||||
use super::{load_timeline, share_encrypted_room};
|
||||
use crate::{
|
||||
Ruma,
|
||||
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite},
|
||||
client::{DEFAULT_BUMP_TYPES, ignored_filter},
|
||||
};
|
||||
|
||||
type TodoRooms = BTreeMap<OwnedRoomId, (BTreeSet<TypeStateKey>, usize, u64)>;
|
||||
@@ -103,13 +102,6 @@ pub(crate) async fn sync_events_v4_route(
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(sender_user)
|
||||
.wide_filter_map(async |(room_id, invite_state)| {
|
||||
if is_ignored_invite(&services, sender_user, &room_id).await {
|
||||
None
|
||||
} else {
|
||||
Some((room_id, invite_state))
|
||||
}
|
||||
})
|
||||
.map(|r| r.0)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
||||
future::ReadyEqExt,
|
||||
math::{ruma_from_usize, usize_from_ruma},
|
||||
stream::WidebandExt,
|
||||
},
|
||||
warn,
|
||||
};
|
||||
@@ -39,7 +38,7 @@
|
||||
use super::share_encrypted_room;
|
||||
use crate::{
|
||||
Ruma,
|
||||
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite, sync::load_timeline},
|
||||
client::{DEFAULT_BUMP_TYPES, ignored_filter, sync::load_timeline},
|
||||
};
|
||||
|
||||
type SyncInfo<'a> = (&'a UserId, &'a DeviceId, u64, &'a sync_events::v5::Request);
|
||||
@@ -107,13 +106,6 @@ pub(crate) async fn sync_events_v5_route(
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(sender_user)
|
||||
.wide_filter_map(async |(room_id, invite_state)| {
|
||||
if is_ignored_invite(services, sender_user, &room_id).await {
|
||||
None
|
||||
} else {
|
||||
Some((room_id, invite_state))
|
||||
}
|
||||
})
|
||||
.map(|r| r.0)
|
||||
.collect::<Vec<OwnedRoomId>>();
|
||||
|
||||
|
||||
@@ -61,16 +61,13 @@ pub(crate) async fn create_invite_route(
|
||||
let mut signed_event = utils::to_canonical_object(&body.event)
|
||||
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
|
||||
|
||||
let recipient_user: OwnedUserId = signed_event
|
||||
let invited_user: OwnedUserId = signed_event
|
||||
.get("state_key")
|
||||
.try_into()
|
||||
.map(UserId::to_owned)
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
|
||||
|
||||
if !services
|
||||
.globals
|
||||
.server_is_ours(recipient_user.server_name())
|
||||
{
|
||||
if !services.globals.server_is_ours(invited_user.server_name()) {
|
||||
return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
|
||||
}
|
||||
|
||||
@@ -78,7 +75,7 @@ pub(crate) async fn create_invite_route(
|
||||
services
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(recipient_user.server_name(), &body.room_id)
|
||||
.acl_check(invited_user.server_name(), &body.room_id)
|
||||
.await?;
|
||||
|
||||
services
|
||||
@@ -92,19 +89,18 @@ pub(crate) async fn create_invite_route(
|
||||
// Add event_id back
|
||||
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
|
||||
|
||||
let sender_user: &UserId = signed_event
|
||||
let sender: &UserId = signed_event
|
||||
.get("sender")
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
|
||||
|
||||
if services.rooms.metadata.is_banned(&body.room_id).await
|
||||
&& !services.users.is_admin(&recipient_user).await
|
||||
&& !services.users.is_admin(&invited_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
if services.config.block_non_admin_invites && !services.users.is_admin(&invited_user).await {
|
||||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
||||
}
|
||||
|
||||
@@ -135,9 +131,9 @@ pub(crate) async fn create_invite_route(
|
||||
.state_cache
|
||||
.update_membership(
|
||||
&body.room_id,
|
||||
&recipient_user,
|
||||
&invited_user,
|
||||
RoomMemberEventContent::new(MembershipState::Invite),
|
||||
sender_user,
|
||||
sender,
|
||||
Some(invite_state),
|
||||
body.via.clone(),
|
||||
true,
|
||||
@@ -145,7 +141,7 @@ pub(crate) async fn create_invite_route(
|
||||
.await?;
|
||||
|
||||
for appservice in services.appservice.read().await.values() {
|
||||
if appservice.is_user_match(&recipient_user) {
|
||||
if appservice.is_user_match(&invited_user) {
|
||||
services
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
|
||||
@@ -73,7 +73,6 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
| ThreepidAuthFailed
|
||||
| UserDeactivated
|
||||
| ThreepidDenied
|
||||
| InviteBlocked
|
||||
| WrongRoomKeysVersion { .. }
|
||||
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
||||
|
||||
|
||||
@@ -1067,8 +1067,7 @@ async fn test_event_sort() {
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE(2025-09-17): Disabled due to unknown "create event must exist" bug
|
||||
// #[tokio::test]
|
||||
#[tokio::test]
|
||||
async fn test_sort() {
|
||||
for _ in 0..20 {
|
||||
// since we shuffle the eventIds before we sort them introducing randomness
|
||||
@@ -1077,8 +1076,7 @@ async fn test_sort() {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(2025-09-17): Disabled due to unknown "create event must exist" bug
|
||||
//#[tokio::test]
|
||||
#[tokio::test]
|
||||
async fn ban_vs_power_level() {
|
||||
let _ = tracing::subscriber::set_default(
|
||||
tracing_subscriber::fmt().with_test_writer().finish(),
|
||||
|
||||
@@ -434,8 +434,4 @@ pub(super) fn open_list(db: &Arc<Engine>, maps: &[Descriptor]) -> Result<Maps> {
|
||||
name: "userroomid_notificationcount",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "userroomid_invitesender",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use itertools::Itertools;
|
||||
use ruma::{
|
||||
OwnedUserId, RoomId, UserId,
|
||||
@@ -138,14 +138,6 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
info!("Migration: Bumped database version to 17");
|
||||
}
|
||||
|
||||
if db["global"]
|
||||
.get(FIXED_CORRUPT_MSC4133_FIELDS_MARKER)
|
||||
.await
|
||||
.is_not_found()
|
||||
{
|
||||
fix_corrupt_msc4133_fields(services).await?;
|
||||
}
|
||||
|
||||
if services.globals.db.database_version().await < 18 {
|
||||
services.globals.db.bump_database_version(18);
|
||||
info!("Migration: Bumped database version to 18");
|
||||
@@ -572,53 +564,3 @@ async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result
|
||||
db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []);
|
||||
db.db.sort()
|
||||
}
|
||||
|
||||
const FIXED_CORRUPT_MSC4133_FIELDS_MARKER: &[u8] = b"fix_corrupt_msc4133_fields";
|
||||
async fn fix_corrupt_msc4133_fields(services: &Services) -> Result {
|
||||
use serde_json::{Value, from_slice};
|
||||
type KeyVal<'a> = ((OwnedUserId, String), &'a [u8]);
|
||||
|
||||
warn!("Fixing corrupted `us.cloke.msc4175.tz` fields...");
|
||||
|
||||
let db = &services.db;
|
||||
let cork = db.cork_and_sync();
|
||||
let useridprofilekey_value = db["useridprofilekey_value"].clone();
|
||||
|
||||
let (total, fixed) = useridprofilekey_value
|
||||
.stream()
|
||||
.try_fold(
|
||||
(0_usize, 0_usize),
|
||||
async |(total, mut fixed),
|
||||
((user, key), value): KeyVal<'_>|
|
||||
-> Result<(usize, usize)> {
|
||||
if let Err(error) = from_slice::<Value>(value) {
|
||||
// Due to an old bug, some conduwuit databases have `us.cloke.msc4175.tz` user
|
||||
// profile fields with raw strings instead of quoted JSON ones.
|
||||
// This migration fixes that.
|
||||
let new_value = if key == "us.cloke.msc4175.tz" {
|
||||
Value::String(String::from_utf8(value.to_vec())?)
|
||||
} else {
|
||||
return Err!(
|
||||
"failed to deserialize msc4133 key {} of user {}: {}",
|
||||
key,
|
||||
user,
|
||||
error
|
||||
);
|
||||
};
|
||||
|
||||
useridprofilekey_value.put((user, key), new_value);
|
||||
}
|
||||
fixed = total.saturating_add(1);
|
||||
|
||||
Ok((total, fixed))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(cork);
|
||||
info!(?total, ?fixed, "Fixed corrupted `us.cloke.msc4175.tz` fields.");
|
||||
|
||||
db["global"].insert(FIXED_CORRUPT_MSC4133_FIELDS_MARKER, []);
|
||||
db.db.sort()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
use database::{Deserialized, Ignore, Interfix, Map};
|
||||
use futures::{Stream, StreamExt, future::join5, pin_mut};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
|
||||
OwnedRoomId, RoomId, ServerName, UserId,
|
||||
events::{AnyStrippedStateEvent, AnySyncStateEvent, room::member::MembershipState},
|
||||
serde::Raw,
|
||||
};
|
||||
@@ -49,7 +49,6 @@ struct Data {
|
||||
userroomid_joined: Arc<Map>,
|
||||
userroomid_leftstate: Arc<Map>,
|
||||
userroomid_knockedstate: Arc<Map>,
|
||||
userroomid_invitesender: Arc<Map>,
|
||||
}
|
||||
|
||||
type AppServiceInRoomCache = SyncRwLock<HashMap<OwnedRoomId, HashMap<String, bool>>>;
|
||||
@@ -84,7 +83,6 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
userroomid_joined: args.db["userroomid_joined"].clone(),
|
||||
userroomid_leftstate: args.db["userroomid_leftstate"].clone(),
|
||||
userroomid_knockedstate: args.db["userroomid_knockedstate"].clone(),
|
||||
userroomid_invitesender: args.db["userroomid_invitesender"].clone(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -525,14 +523,3 @@ pub async fn is_left(&self, user_id: &UserId, room_id: &RoomId) -> bool {
|
||||
let key = (user_id, room_id);
|
||||
self.db.userroomid_leftstate.qry(&key).await.is_ok()
|
||||
}
|
||||
|
||||
#[implement(Service)]
|
||||
#[tracing::instrument(skip(self), level = "trace")]
|
||||
pub async fn invite_sender(&self, user_id: &UserId, room_id: &RoomId) -> Result<OwnedUserId> {
|
||||
let key = (user_id, room_id);
|
||||
self.db
|
||||
.userroomid_invitesender
|
||||
.qry(&key)
|
||||
.await
|
||||
.deserialized()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use conduwuit::{Err, Result, implement, is_not_empty, utils::ReadyExt, warn};
|
||||
use conduwuit::{Result, implement, is_not_empty, utils::ReadyExt, warn};
|
||||
use database::{Json, serialize_key};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
@@ -9,7 +9,6 @@
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
direct::DirectEvent,
|
||||
invite_permission_config::FilterLevel,
|
||||
room::{
|
||||
create::RoomCreateEventContent,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
@@ -122,21 +121,12 @@ pub async fn update_membership(
|
||||
self.mark_as_joined(user_id, room_id);
|
||||
},
|
||||
| MembershipState::Invite => {
|
||||
// return an error for blocked invites. ignored invites aren't handled here
|
||||
// since the recipient's membership should still be changed to `invite`.
|
||||
// they're filtered out in the individual /sync handlers
|
||||
if matches!(
|
||||
self.services
|
||||
.users
|
||||
.invite_filter_level(sender, user_id)
|
||||
.await,
|
||||
FilterLevel::Block
|
||||
) {
|
||||
return Err!(Request(InviteBlocked(
|
||||
"{user_id} has blocked invites from {sender}."
|
||||
)));
|
||||
// We want to know if the sender is ignored by the receiver
|
||||
if self.services.users.user_is_ignored(sender, user_id).await {
|
||||
return Ok(());
|
||||
}
|
||||
self.mark_as_invited(user_id, room_id, sender, last_state, invite_via)
|
||||
|
||||
self.mark_as_invited(user_id, room_id, last_state, invite_via)
|
||||
.await;
|
||||
},
|
||||
| MembershipState::Leave | MembershipState::Ban => {
|
||||
@@ -241,7 +231,6 @@ pub fn mark_as_joined(&self, user_id: &UserId, room_id: &RoomId) {
|
||||
|
||||
self.db.userroomid_invitestate.remove(&userroom_id);
|
||||
self.db.roomuserid_invitecount.remove(&roomuser_id);
|
||||
self.db.userroomid_invitesender.remove(&userroom_id);
|
||||
|
||||
self.db.userroomid_leftstate.remove(&userroom_id);
|
||||
self.db.roomuserid_leftcount.remove(&roomuser_id);
|
||||
@@ -279,7 +268,6 @@ pub fn mark_as_left(&self, user_id: &UserId, room_id: &RoomId) {
|
||||
|
||||
self.db.userroomid_invitestate.remove(&userroom_id);
|
||||
self.db.roomuserid_invitecount.remove(&roomuser_id);
|
||||
self.db.userroomid_invitesender.remove(&userroom_id);
|
||||
|
||||
self.db.userroomid_knockedstate.remove(&userroom_id);
|
||||
self.db.roomuserid_knockedcount.remove(&roomuser_id);
|
||||
@@ -316,7 +304,6 @@ pub fn mark_as_knocked(
|
||||
|
||||
self.db.userroomid_invitestate.remove(&userroom_id);
|
||||
self.db.roomuserid_invitecount.remove(&roomuser_id);
|
||||
self.db.userroomid_invitesender.remove(&userroom_id);
|
||||
|
||||
self.db.userroomid_leftstate.remove(&userroom_id);
|
||||
self.db.roomuserid_leftcount.remove(&roomuser_id);
|
||||
@@ -348,7 +335,6 @@ pub async fn mark_as_invited(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
sender_user: &UserId,
|
||||
last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
|
||||
invite_via: Option<Vec<OwnedServerName>>,
|
||||
) {
|
||||
@@ -364,9 +350,6 @@ pub async fn mark_as_invited(
|
||||
self.db
|
||||
.roomuserid_invitecount
|
||||
.raw_aput::<8, _, _>(&roomuser_id, self.services.globals.next_count().unwrap());
|
||||
self.db
|
||||
.userroomid_invitesender
|
||||
.raw_put(&userroom_id, sender_user);
|
||||
|
||||
self.db.userroomid_joined.remove(&userroom_id);
|
||||
self.db.roomuserid_joined.remove(&roomuser_id);
|
||||
|
||||
+34
-25
@@ -20,9 +20,7 @@
|
||||
api::client::{device::Device, error::ErrorKind, filter::FilterDefinition},
|
||||
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
||||
events::{
|
||||
AnyToDeviceEvent, GlobalAccountDataEventType,
|
||||
ignored_user_list::IgnoredUserListEvent,
|
||||
invite_permission_config::{FilterLevel, InvitePermissionConfigEvent},
|
||||
AnyToDeviceEvent, GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
@@ -141,26 +139,6 @@ pub async fn user_is_ignored(&self, sender_user: &UserId, recipient_user: &UserI
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the recipient's filter level for an invite from the sender.
|
||||
pub async fn invite_filter_level(
|
||||
&self,
|
||||
sender_user: &UserId,
|
||||
recipient_user: &UserId,
|
||||
) -> FilterLevel {
|
||||
if self.user_is_ignored(sender_user, recipient_user).await {
|
||||
FilterLevel::Ignore
|
||||
} else {
|
||||
self.services
|
||||
.account_data
|
||||
.get_global(recipient_user, GlobalAccountDataEventType::InvitePermissionConfig)
|
||||
.await
|
||||
.map(|config: InvitePermissionConfigEvent| {
|
||||
config.content.user_filter_level(sender_user)
|
||||
})
|
||||
.unwrap_or(FilterLevel::Allow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a user is an admin
|
||||
#[inline]
|
||||
pub async fn is_admin(&self, user_id: &UserId) -> bool {
|
||||
@@ -1124,6 +1102,34 @@ pub async fn find_from_login_token(&self, token: &str) -> Result<OwnedUserId> {
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_profile_kv(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
key: &str,
|
||||
value: Vec<u8>,
|
||||
) -> Result<serde_json::Value> {
|
||||
match serde_json::from_slice(&value) {
|
||||
| Ok(value) => Ok(value),
|
||||
| Err(error) => {
|
||||
// Due to an old bug, some conduwuit databases have `us.cloke.msc4175.tz` user
|
||||
// profile fields with raw strings instead of quoted JSON ones.
|
||||
if key == "us.cloke.msc4175.tz" {
|
||||
// TODO insert a hint about this being a cold path
|
||||
debug_warn!(
|
||||
"Fixing corrupt `us.cloke.msc4175.tz` field in the profile of {}",
|
||||
user_id
|
||||
);
|
||||
let raw_tz = serde_json::Value::String(String::from_utf8(value)?);
|
||||
self.set_profile_key(user_id, "us.cloke.msc4175.tz", Some(raw_tz.clone()));
|
||||
Ok(raw_tz)
|
||||
} else {
|
||||
Err(error.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a specific user profile key
|
||||
pub async fn profile_key(
|
||||
&self,
|
||||
@@ -1135,7 +1141,7 @@ pub async fn profile_key(
|
||||
.useridprofilekey_value
|
||||
.qry(&key)
|
||||
.await
|
||||
.and_then(|handle| serde_json::from_slice(&handle).map_err(Into::into))
|
||||
.and_then(|handle| self.parse_profile_kv(user_id, profile_key, handle.to_vec()))
|
||||
}
|
||||
|
||||
/// Gets all the user's profile keys and values in an iterator
|
||||
@@ -1150,7 +1156,10 @@ pub fn all_profile_keys<'a>(
|
||||
.useridprofilekey_value
|
||||
.stream_prefix(&prefix)
|
||||
.ignore_err()
|
||||
.map(|((_, key), value): KeyVal<'_>| Ok((key, serde_json::from_slice(value)?)))
|
||||
.map(|((_, key), value): KeyVal<'_>| {
|
||||
let value = self.parse_profile_kv(user_id, &key, value.to_vec())?;
|
||||
Ok((key, value))
|
||||
})
|
||||
.ignore_err()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user