mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-01 20:26:18 +00:00
Compare commits
37 Commits
nex/fix/up
...
jade/flake
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7cc68cbf | ||
|
|
dc047b635f | ||
|
|
cc4c2fed25 | ||
|
|
17e47ecd6d | ||
|
|
b1d5ff477b | ||
|
|
d6dc01ac2c | ||
|
|
77ebe0d02f | ||
|
|
81e3d4c905 | ||
|
|
cb8f36444c | ||
|
|
799def70dc | ||
|
|
20f741d0e5 | ||
|
|
d38f4a24f2 | ||
|
|
6604cc4df9 | ||
|
|
89aa4d1eae | ||
|
|
9231ea5114 | ||
|
|
4a3c72338d | ||
|
|
ab862f4383 | ||
|
|
bd43be931a | ||
|
|
148240cbbb | ||
|
|
2e9e42d9ae | ||
|
|
89fbda0d6e | ||
|
|
c97eb5c889 | ||
|
|
366ec46b26 | ||
|
|
62a98ebc71 | ||
|
|
439c605efe | ||
|
|
32df2f3487 | ||
|
|
692da7ffc2 | ||
|
|
1082b24b1d | ||
|
|
f45ceedb8a | ||
|
|
d614e43981 | ||
|
|
1e0e7a31aa | ||
|
|
92fffe9c82 | ||
|
|
11e51300a5 | ||
|
|
ef84e1bb02 | ||
|
|
1887d58df8 | ||
|
|
c66f6f8900 | ||
|
|
902fe7b7ab |
@@ -32,7 +32,7 @@ jobs:
|
||||
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
|
||||
|
||||
- name: Checkout repository with full history
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
uses: https://code.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
path: ${{ steps.cargo-deb.outputs.path }}
|
||||
|
||||
- name: Publish to Forgejo package registry
|
||||
if: ${{ forge.event_name == 'push' || forge.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ forge.event_name == 'push' || forge.event_name == 'workflow_dispatch' || forge.event_name == 'schedule' }}
|
||||
run: |
|
||||
OWNER="continuwuation"
|
||||
DISTRIBUTION=${{ steps.debian-version.outputs.distribution }}
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
echo "Fedora version: $VERSION"
|
||||
|
||||
- name: Checkout repository with full history
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
uses: https://code.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
path: artifacts/*debuginfo*.rpm
|
||||
|
||||
- name: Publish to RPM Package Registry
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
|
||||
run: |
|
||||
# Find the main binary RPM (exclude debug and source RPMs)
|
||||
RPM=$(find artifacts -name "continuwuity-*.rpm" \
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:41.127.2@sha256:66bc84e2f889025fbb3c9df863500dcc18bc64ac85bcf629d015064377d77f31
|
||||
image: ghcr.io/renovatebot/renovate:41.146.4@sha256:bb70194b7405faf10a6f279b60caa10403a440ba37d158c5a4ef0ae7b67a0f92
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
- "Cargo.lock"
|
||||
- "Cargo.toml"
|
||||
- "rust-toolchain.toml"
|
||||
- ".forgejo/workflows/update-flake-hashes.yml"
|
||||
|
||||
jobs:
|
||||
update-flake-hashes:
|
||||
@@ -14,13 +15,13 @@ jobs:
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: 0
|
||||
fetch-tags: false
|
||||
fetch-single-branch: true
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
|
||||
- uses: https://github.com/cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2
|
||||
- uses: https://github.com/cachix/install-nix-action@7ab6e7fd29da88e74b1e314a4ae9ac6b5cda3801 # v31.8.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
925
Cargo.lock
generated
925
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -551,9 +551,9 @@ features = ["std"]
|
||||
version = "1.0.2"
|
||||
|
||||
[workspace.dependencies.ldap3]
|
||||
version = "0.11.5"
|
||||
version = "0.12.0"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
features = ["sync", "tls-rustls", "rustls-provider"]
|
||||
|
||||
[workspace.dependencies.resolv-conf]
|
||||
version = "0.7.5"
|
||||
|
||||
@@ -48,7 +48,7 @@ EOF
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.15.5
|
||||
ENV BINSTALL_VERSION=1.15.7
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
|
||||
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.15.5
|
||||
ENV BINSTALL_VERSION=1.15.7
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
|
||||
30
flake.lock
generated
30
flake.lock
generated
@@ -10,11 +10,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1757683818,
|
||||
"narHash": "sha256-q7q0pWT+wu5AUU1Qlbwq8Mqb+AzHKhaMCVUq/HNZfo8=",
|
||||
"lastModified": 1758711588,
|
||||
"narHash": "sha256-0nZlCCDC5PfndsQJXXtcyrtrfW49I3KadGMDlutzaGU=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "7c5d79ad62cda340cb8c80c99b921b7b7ffacf69",
|
||||
"rev": "12cbeca141f46e1ade76728bce8adc447f2166c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -99,11 +99,11 @@
|
||||
},
|
||||
"crane_2": {
|
||||
"locked": {
|
||||
"lastModified": 1757183466,
|
||||
"narHash": "sha256-kTdCCMuRE+/HNHES5JYsbRHmgtr+l9mOtf5dpcMppVc=",
|
||||
"lastModified": 1759893430,
|
||||
"narHash": "sha256-yAy4otLYm9iZ+NtQwTMEbqHwswSFUbhn7x826RR6djw=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "d599ae4847e7f87603e7082d73ca673aa93c916d",
|
||||
"rev": "1979a2524cb8c801520bd94c38bb3d5692419d93",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -152,11 +152,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1758004879,
|
||||
"narHash": "sha256-kV7tQzcNbmo58wg2uE2MQ/etaTx+PxBMHeNrLP8vOgk=",
|
||||
"lastModified": 1760337631,
|
||||
"narHash": "sha256-3nvEN2lEpWtM1x7nfuiwpYHLNDgEUiWeBbyvy4vtVw8=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "07e5ce53dd020e6b337fdddc934561bee0698fa2",
|
||||
"rev": "fee7cf67cbd80a74460563388ac358b394014238",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -455,11 +455,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1758029226,
|
||||
"narHash": "sha256-TjqVmbpoCqWywY9xIZLTf6ANFvDCXdctCjoYuYPYdMI=",
|
||||
"lastModified": 1760256791,
|
||||
"narHash": "sha256-uTpzDHRASEDeFUuToWSQ46Re8beXyG9dx4W36FQa0/c=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "08b8f92ac6354983f5382124fef6006cade4a1c1",
|
||||
"rev": "832e3b6db48508ae436c2c7bfc0cf914eac6938e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -484,11 +484,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1757362324,
|
||||
"narHash": "sha256-/PAhxheUq4WBrW5i/JHzcCqK5fGWwLKdH6/Lu1tyS18=",
|
||||
"lastModified": 1760260966,
|
||||
"narHash": "sha256-pOVvZz/aa+laeaUKyE6PtBevdo4rywMwjhWdSZE/O1c=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "9edc9cbe5d8e832b5864e09854fa94861697d2fd",
|
||||
"rev": "c5181dbbe33af6f21b9d83e02fdb6fda298a3b65",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -64,12 +64,8 @@
|
||||
"matchDatasources": ["docker"],
|
||||
"matchPackageNames": ["ghcr.io/renovatebot/renovate"],
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward"
|
||||
},
|
||||
{
|
||||
"description": "Group lockfile updates into a single PR",
|
||||
"matchUpdateTypes": ["lockFileMaintenance"],
|
||||
"groupName": "lockfile-maintenance"
|
||||
"automergeStrategy": "fast-forward",
|
||||
"extends": ["schedule:earlyMondays"]
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
@@ -81,7 +77,7 @@
|
||||
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=(?<datasource>[a-z-.]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s"
|
||||
"# renovate: datasource=(?<datasource>[a-zA-Z0-9-._]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s+(?:(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_CHECKSUM[ =][\"']?(?<currentDigest>.+?)[\"']?\\s)?"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -64,10 +64,14 @@ pub(crate) async fn create_content_route(
|
||||
media_id: &utils::random_string(MXC_LENGTH),
|
||||
};
|
||||
|
||||
services
|
||||
if let Err(e) = services
|
||||
.media
|
||||
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
err!("Failed to save uploaded media: {e}");
|
||||
return Err!(Request(Unknown("Failed to save uploaded media")));
|
||||
}
|
||||
|
||||
let blurhash = body.generate_blurhash.then(|| {
|
||||
services
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Event, Result, debug, err, info,
|
||||
Err, Error, Event, Result, RoomVersion, debug, err, info,
|
||||
matrix::{StateKey, pdu::PduBuilder},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
@@ -68,37 +68,77 @@ pub(crate) async fn upgrade_room_route(
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
|
||||
// First, check if the user has permission to upgrade the room (send tombstone
|
||||
// event)
|
||||
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
// Check tombstone permission by attempting to create (but not send) the event
|
||||
// Note that this does internally call the policy server with a fake room ID,
|
||||
// which may not be good?
|
||||
let tombstone_test_result = services
|
||||
.rooms
|
||||
.timeline
|
||||
.create_hash_and_sign_event(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: RoomId::new(services.globals.server_name()),
|
||||
}),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(_e) = tombstone_test_result {
|
||||
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
|
||||
}
|
||||
|
||||
drop(old_room_state_lock);
|
||||
|
||||
// Create a replacement room
|
||||
let replacement_room = RoomId::new(services.globals.server_name());
|
||||
let room_features = RoomVersion::new(&body.new_version)?;
|
||||
let replacement_room_owned = if !room_features.room_ids_as_hashes {
|
||||
Some(RoomId::new(services.globals.server_name()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
|
||||
let replacement_room_tmp = match replacement_room {
|
||||
| Some(v) => v,
|
||||
| None => &RoomId::new(services.globals.server_name()),
|
||||
};
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(&replacement_room)
|
||||
.get_or_create_shortroomid(replacement_room_tmp)
|
||||
.await;
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
// 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 = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.clone(),
|
||||
}),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
|
||||
// For pre-v12 rooms, send tombstone before creating replacement room
|
||||
let tombstone_event_id = if !room_features.room_ids_as_hashes {
|
||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||
// intended to be used any further
|
||||
let tombstone_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.unwrap().to_owned(),
|
||||
}),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
Some(tombstone_event_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let state_lock = services.rooms.state.mutex.lock(replacement_room_tmp).await;
|
||||
|
||||
// Get the old room creation event
|
||||
let mut create_event_content: CanonicalJsonObject = services
|
||||
@@ -111,7 +151,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
// Use the m.room.tombstone event as the predecessor
|
||||
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
|
||||
body.room_id.clone(),
|
||||
Some(tombstone_event_id),
|
||||
tombstone_event_id,
|
||||
));
|
||||
|
||||
// Send a m.room.create event containing a predecessor field and the applicable
|
||||
@@ -132,6 +172,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
// "creator" key no longer exists in V11 rooms
|
||||
create_event_content.remove("creator");
|
||||
},
|
||||
// TODO(hydra): additional_creators
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +200,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||
}
|
||||
|
||||
services
|
||||
let create_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
@@ -173,11 +214,18 @@ pub(crate) async fn upgrade_room_route(
|
||||
timestamp: None,
|
||||
},
|
||||
sender_user,
|
||||
Some(&replacement_room),
|
||||
replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
let create_id = create_event_id.as_str().replace('$', "!");
|
||||
let (replacement_room, state_lock) = if room_features.room_ids_as_hashes {
|
||||
let parsed_room_id = RoomId::parse(&create_id)?;
|
||||
(Some(parsed_room_id), services.rooms.state.mutex.lock(parsed_room_id).await)
|
||||
} else {
|
||||
(replacement_room, state_lock)
|
||||
};
|
||||
|
||||
// Join the new room
|
||||
services
|
||||
@@ -204,7 +252,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
timestamp: None,
|
||||
},
|
||||
sender_user,
|
||||
Some(&replacement_room),
|
||||
replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
@@ -243,7 +291,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
Some(&replacement_room),
|
||||
replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
@@ -268,7 +316,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(alias, &replacement_room, sender_user)?;
|
||||
.set_alias(alias, replacement_room.unwrap(), sender_user)?;
|
||||
}
|
||||
|
||||
// Get the old room power levels
|
||||
@@ -310,6 +358,27 @@ pub(crate) async fn upgrade_room_route(
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// For v12 rooms, send tombstone AFTER creating replacement room
|
||||
if room_features.room_ids_as_hashes {
|
||||
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
|
||||
// could best effort one maybe?
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.unwrap().to_owned(),
|
||||
}),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
)
|
||||
.await?;
|
||||
drop(old_room_state_lock);
|
||||
}
|
||||
|
||||
// Check if the old room has a space parent, and if so, whether we should update
|
||||
// it (m.space.parent, room_id)
|
||||
let parents = services
|
||||
@@ -334,8 +403,9 @@ pub(crate) async fn upgrade_room_route(
|
||||
continue;
|
||||
};
|
||||
debug!(
|
||||
"Updating space {space_id} child event for room {} to {replacement_room}",
|
||||
&body.room_id
|
||||
"Updating space {space_id} child event for room {} to {}",
|
||||
&body.room_id,
|
||||
replacement_room.unwrap()
|
||||
);
|
||||
// First, drop the space's child event
|
||||
let state_lock = services.rooms.state.mutex.lock(space_id).await;
|
||||
@@ -359,7 +429,10 @@ pub(crate) async fn upgrade_room_route(
|
||||
.await
|
||||
.ok();
|
||||
// Now, add a new child event for the replacement room
|
||||
debug!("Adding space child event for room {replacement_room} in space {space_id}");
|
||||
debug!(
|
||||
"Adding space child event for room {} in space {space_id}",
|
||||
replacement_room.unwrap()
|
||||
);
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
@@ -372,7 +445,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
suggested: child.suggested,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
state_key: Some(replacement_room.as_str().into()),
|
||||
state_key: Some(replacement_room.unwrap().as_str().into()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
@@ -383,12 +456,15 @@ pub(crate) async fn upgrade_room_route(
|
||||
.await
|
||||
.ok();
|
||||
debug!(
|
||||
"Finished updating space {space_id} child event for room {} to {replacement_room}",
|
||||
&body.room_id
|
||||
"Finished updating space {space_id} child event for room {} to {}",
|
||||
&body.room_id,
|
||||
replacement_room.unwrap()
|
||||
);
|
||||
drop(state_lock);
|
||||
}
|
||||
|
||||
// Return the replacement room id
|
||||
Ok(upgrade_room::v3::Response { replacement_room })
|
||||
Ok(upgrade_room::v3::Response {
|
||||
replacement_room: replacement_room.unwrap().to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -320,6 +320,7 @@ async fn handle_lists<'a, Rooms, AllRooms>(
|
||||
|
||||
for mut range in ranges {
|
||||
range.0 = uint!(0);
|
||||
range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1);
|
||||
range.1 = range
|
||||
.1
|
||||
.clamp(range.0, UInt::try_from(active_rooms.len()).unwrap_or(UInt::MAX));
|
||||
|
||||
@@ -34,6 +34,19 @@ pub(super) async fn from(
|
||||
|
||||
let max_body_size = services.server.config.max_request_size;
|
||||
|
||||
// Check if the Content-Length header is present and valid, saves us streaming
|
||||
// the response into memory
|
||||
if let Some(content_length) = parts.headers.get(http::header::CONTENT_LENGTH) {
|
||||
if let Ok(content_length) = content_length
|
||||
.to_str()
|
||||
.map(|s| s.parse::<usize>().unwrap_or_default())
|
||||
{
|
||||
if content_length > max_body_size {
|
||||
return Err(err!(Request(TooLarge("Request body too large"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let body = axum::body::to_bytes(body, max_body_size)
|
||||
.await
|
||||
.map_err(|e| err!(Request(TooLarge("Request body too large: {e}"))))?;
|
||||
|
||||
@@ -200,11 +200,15 @@ pub async fn auth_check<E, F, Fut>(
|
||||
if incoming_event.room_id().is_some() {
|
||||
let Some(room_id_server_name) = incoming_event.room_id().unwrap().server_name()
|
||||
else {
|
||||
warn!("room ID has no servername");
|
||||
warn!("legacy room ID has no server name");
|
||||
return Ok(false);
|
||||
};
|
||||
if room_id_server_name != sender.server_name() {
|
||||
warn!("servername of room ID does not match servername of sender");
|
||||
warn!(
|
||||
expected = %sender.server_name(),
|
||||
received = %room_id_server_name,
|
||||
"server name of legacy room ID does not match server name of sender"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
@@ -215,12 +219,12 @@ pub async fn auth_check<E, F, Fut>(
|
||||
.room_version
|
||||
.is_some_and(|v| v.deserialize().is_err())
|
||||
{
|
||||
warn!("invalid room version found in m.room.create event");
|
||||
warn!("unsupported room version found in m.room.create event");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if room_version.room_ids_as_hashes && incoming_event.room_id().is_some() {
|
||||
warn!("room create event incorrectly claims a room ID");
|
||||
warn!("room create event incorrectly claims to have a room ID when it should not");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -229,7 +233,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
{
|
||||
// If content has no creator field, reject
|
||||
if content.creator.is_none() {
|
||||
warn!("no creator field found in m.room.create content");
|
||||
warn!("m.room.create event incorrectly omits 'creator' field");
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
@@ -282,16 +286,19 @@ pub async fn auth_check<E, F, Fut>(
|
||||
.room_version
|
||||
.is_some_and(|v| v.deserialize().is_err())
|
||||
{
|
||||
warn!("invalid room version found in m.room.create event");
|
||||
warn!(
|
||||
create_event_id = %room_create_event.event_id(),
|
||||
"unsupported room version found in m.room.create event"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
let expected_room_id = room_create_event.room_id_or_hash();
|
||||
|
||||
if incoming_event.room_id().unwrap() != expected_room_id {
|
||||
if incoming_event.room_id().expect("event must have a room ID") != expected_room_id {
|
||||
warn!(
|
||||
expected = %expected_room_id,
|
||||
received = %incoming_event.room_id().unwrap(),
|
||||
"room_id of incoming event ({}) does not match room_id of m.room.create event ({})",
|
||||
"room_id of incoming event ({}) does not match that of the m.room.create event ({})",
|
||||
incoming_event.room_id().unwrap(),
|
||||
expected_room_id,
|
||||
);
|
||||
@@ -304,12 +311,15 @@ pub async fn auth_check<E, F, Fut>(
|
||||
.auth_events()
|
||||
.any(|id| id == room_create_event.event_id());
|
||||
if room_version.room_ids_as_hashes && claims_create_event {
|
||||
warn!("m.room.create event incorrectly found in auth events");
|
||||
warn!("event incorrectly references m.room.create event in auth events");
|
||||
return Ok(false);
|
||||
} else if !room_version.room_ids_as_hashes && !claims_create_event {
|
||||
// If the create event is not referenced in the event's auth events, and this is
|
||||
// a v11 room, reject
|
||||
warn!("no m.room.create event found in auth events");
|
||||
warn!(
|
||||
missing = %room_create_event.event_id(),
|
||||
"event incorrectly did not reference an m.room.create in its auth events"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -318,7 +328,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
warn!(
|
||||
expected = %expected_room_id,
|
||||
received = %pe.room_id().unwrap(),
|
||||
"room_id of power levels event does not match room_id of m.room.create event"
|
||||
"room_id of referenced power levels event does not match that of the m.room.create event"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -332,8 +342,9 @@ pub async fn auth_check<E, F, Fut>(
|
||||
&& room_create_event.sender().server_name() != incoming_event.sender().server_name()
|
||||
{
|
||||
warn!(
|
||||
"room is not federated and event's sender domain does not match create event's \
|
||||
sender domain"
|
||||
sender = %incoming_event.sender(),
|
||||
create_sender = %room_create_event.sender(),
|
||||
"room is not federated and event's sender domain does not match create event's sender domain"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -416,7 +427,6 @@ pub async fn auth_check<E, F, Fut>(
|
||||
&user_for_join_auth_membership,
|
||||
&room_create_event,
|
||||
)? {
|
||||
warn!("membership change not valid for some reason");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -429,7 +439,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
let sender_member_event = match sender_member_event {
|
||||
| Some(mem) => mem,
|
||||
| None => {
|
||||
warn!("sender not found in room");
|
||||
warn!("sender has no membership event");
|
||||
return Ok(false);
|
||||
},
|
||||
};
|
||||
@@ -440,7 +450,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
!= expected_room_id
|
||||
{
|
||||
warn!(
|
||||
"room_id of incoming event ({}) does not match room_id of m.room.create event ({})",
|
||||
"room_id of incoming event ({}) does not match that of the m.room.create event ({})",
|
||||
sender_member_event
|
||||
.room_id()
|
||||
.expect("event must have a room ID"),
|
||||
@@ -453,8 +463,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
from_json_str(sender_member_event.content().get())?;
|
||||
let Some(membership_state) = sender_membership_event_content.membership else {
|
||||
warn!(
|
||||
sender_membership_event_content = format!("{sender_membership_event_content:?}"),
|
||||
event_id = format!("{}", incoming_event.event_id()),
|
||||
?sender_membership_event_content,
|
||||
"Sender membership event content missing membership field"
|
||||
);
|
||||
return Err(Error::InvalidPdu("Missing membership field".to_owned()));
|
||||
@@ -462,7 +471,11 @@ pub async fn auth_check<E, F, Fut>(
|
||||
let membership_state = membership_state.deserialize()?;
|
||||
|
||||
if !matches!(membership_state, MembershipState::Join) {
|
||||
warn!("sender's membership is not join");
|
||||
warn!(
|
||||
%sender,
|
||||
?membership_state,
|
||||
"sender cannot send events without being joined to the room"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -522,7 +535,12 @@ pub async fn auth_check<E, F, Fut>(
|
||||
};
|
||||
|
||||
if sender_power_level < invite_level {
|
||||
warn!("sender's cannot send invites in this room");
|
||||
warn!(
|
||||
%sender,
|
||||
has=?sender_power_level,
|
||||
required=?invite_level,
|
||||
"sender cannot send invites in this room"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -534,7 +552,11 @@ pub async fn auth_check<E, F, Fut>(
|
||||
// level, reject If the event has a state_key that starts with an @ and does
|
||||
// not match the sender, reject.
|
||||
if !can_send_event(incoming_event, power_levels_event.as_ref(), sender_power_level) {
|
||||
warn!("user cannot send event");
|
||||
warn!(
|
||||
%sender,
|
||||
event_type=?incoming_event.kind(),
|
||||
"sender cannot send event"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -579,6 +601,12 @@ pub async fn auth_check<E, F, Fut>(
|
||||
};
|
||||
|
||||
if !check_redaction(room_version, incoming_event, sender_power_level, redact_level)? {
|
||||
warn!(
|
||||
%sender,
|
||||
?sender_power_level,
|
||||
?redact_level,
|
||||
"redaction event was not allowed"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
@@ -759,7 +787,7 @@ struct GetThirdPartyInvite {
|
||||
|
||||
if prev_event_is_create_event && no_more_prev_events {
|
||||
trace!(
|
||||
sender = %sender,
|
||||
%sender,
|
||||
target_user = %target_user,
|
||||
?sender_creator,
|
||||
?target_creator,
|
||||
@@ -779,22 +807,33 @@ struct GetThirdPartyInvite {
|
||||
);
|
||||
if sender != target_user {
|
||||
// If the sender does not match state_key, reject.
|
||||
warn!("Can't make other user join");
|
||||
warn!(
|
||||
%sender,
|
||||
target_user = %target_user,
|
||||
"sender cannot join on behalf of another user"
|
||||
);
|
||||
false
|
||||
} else if target_user_current_membership == MembershipState::Ban {
|
||||
// If the sender is banned, reject.
|
||||
warn!(?target_user_membership_event_id, "Banned user can't join");
|
||||
warn!(
|
||||
%sender,
|
||||
membership_event_id = ?target_user_membership_event_id,
|
||||
"sender cannot join as they are banned from the room"
|
||||
);
|
||||
false
|
||||
} else {
|
||||
match join_rules {
|
||||
| JoinRule::Invite =>
|
||||
if !membership_allows_join {
|
||||
warn!(
|
||||
membership=?target_user_current_membership,
|
||||
"Join rule is invite but membership does not allow join"
|
||||
%sender,
|
||||
membership_event_id = ?target_user_membership_event_id,
|
||||
membership = ?target_user_current_membership,
|
||||
"sender cannot join as they are not invited to the invite-only room"
|
||||
);
|
||||
false
|
||||
} else {
|
||||
trace!(sender=%sender, "sender is invited to room, allowing join");
|
||||
true
|
||||
},
|
||||
| JoinRule::Knock if !room_version.allow_knocking => {
|
||||
@@ -804,11 +843,14 @@ struct GetThirdPartyInvite {
|
||||
| JoinRule::Knock =>
|
||||
if !membership_allows_join {
|
||||
warn!(
|
||||
%sender,
|
||||
membership_event_id = ?target_user_membership_event_id,
|
||||
membership=?target_user_current_membership,
|
||||
"Join rule is knock but membership does not allow join"
|
||||
"sender cannot join a knock room without being invited or already joined"
|
||||
);
|
||||
false
|
||||
} else {
|
||||
trace!(sender=%sender, "sender is invited or already joined to room, allowing join");
|
||||
true
|
||||
},
|
||||
| JoinRule::KnockRestricted(_) if !room_version.knock_restricted_join_rule =>
|
||||
@@ -820,33 +862,55 @@ struct GetThirdPartyInvite {
|
||||
},
|
||||
| JoinRule::KnockRestricted(_) => {
|
||||
if membership_allows_join || user_for_join_auth_is_valid {
|
||||
trace!(
|
||||
%sender,
|
||||
%membership_allows_join,
|
||||
%user_for_join_auth_is_valid,
|
||||
"sender is invited, already joined to, or authorised to join the room, allowing join"
|
||||
);
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
%sender,
|
||||
membership_event_id = ?target_user_membership_event_id,
|
||||
membership=?target_user_current_membership,
|
||||
"Join rule is a restricted one, but no valid authorising user \
|
||||
was given and the sender's current membership does not permit \
|
||||
a join transition"
|
||||
%user_for_join_auth_is_valid,
|
||||
?user_for_join_auth,
|
||||
"sender cannot join as they are not invited nor already joined to the room, nor was a \
|
||||
valid authorising user given to permit the join"
|
||||
);
|
||||
false
|
||||
}
|
||||
},
|
||||
| JoinRule::Restricted(_) =>
|
||||
if membership_allows_join || user_for_join_auth_is_valid {
|
||||
trace!(
|
||||
%sender,
|
||||
%membership_allows_join,
|
||||
%user_for_join_auth_is_valid,
|
||||
"sender is invited, already joined to, or authorised to join the room, allowing join"
|
||||
);
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"Join rule is a restricted one but no valid authorising user \
|
||||
was given"
|
||||
%sender,
|
||||
membership_event_id = ?target_user_membership_event_id,
|
||||
membership=?target_user_current_membership,
|
||||
%user_for_join_auth_is_valid,
|
||||
?user_for_join_auth,
|
||||
"sender cannot join as they are not invited nor already joined to the room, nor was a \
|
||||
valid authorising user given to permit the join"
|
||||
);
|
||||
false
|
||||
},
|
||||
| JoinRule::Public => true,
|
||||
| JoinRule::Public => {
|
||||
trace!(%sender, "join rule is public, allowing join");
|
||||
true
|
||||
},
|
||||
| _ => {
|
||||
warn!(
|
||||
join_rule=?join_rules,
|
||||
membership=?target_user_current_membership,
|
||||
"Unknown join rule doesn't allow joining, or the rule's conditions were not met"
|
||||
"Join rule is unknown, or the rule's conditions were not met"
|
||||
);
|
||||
false
|
||||
},
|
||||
@@ -873,16 +937,23 @@ struct GetThirdPartyInvite {
|
||||
}
|
||||
allow
|
||||
},
|
||||
| _ => {
|
||||
if !sender_is_joined
|
||||
|| target_user_current_membership == MembershipState::Join
|
||||
|| target_user_current_membership == MembershipState::Ban
|
||||
{
|
||||
| _ =>
|
||||
if !sender_is_joined {
|
||||
warn!(
|
||||
%sender,
|
||||
?sender_membership_event_id,
|
||||
?sender_membership,
|
||||
"sender cannot produce an invite without being joined to the room",
|
||||
);
|
||||
false
|
||||
} else if matches!(
|
||||
target_user_current_membership,
|
||||
MembershipState::Join | MembershipState::Ban
|
||||
) {
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
?sender_membership_event_id,
|
||||
"Can't invite user if sender not joined or the user is currently \
|
||||
joined or banned",
|
||||
?target_user_current_membership,
|
||||
"cannot invite a user who is banned or already joined",
|
||||
);
|
||||
false
|
||||
} else {
|
||||
@@ -892,56 +963,107 @@ struct GetThirdPartyInvite {
|
||||
.is_some();
|
||||
if !allow {
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
?power_levels_event_id,
|
||||
"User does not have enough power to invite",
|
||||
%sender,
|
||||
has=?sender_power,
|
||||
required=?power_levels.invite,
|
||||
"sender does not have enough power to produce invites",
|
||||
);
|
||||
}
|
||||
trace!(
|
||||
%sender,
|
||||
?sender_membership_event_id,
|
||||
?sender_membership,
|
||||
?target_user_membership_event_id,
|
||||
?target_user_current_membership,
|
||||
sender_pl=?sender_power,
|
||||
required_pl=?power_levels.invite,
|
||||
"allowing invite"
|
||||
);
|
||||
allow
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
| MembershipState::Leave =>
|
||||
| MembershipState::Leave => {
|
||||
let can_unban = if target_user_current_membership == MembershipState::Ban {
|
||||
sender_creator || sender_power.filter(|&p| p < &power_levels.ban).is_some()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let can_kick = if !matches!(
|
||||
target_user_current_membership,
|
||||
MembershipState::Ban | MembershipState::Leave
|
||||
) {
|
||||
sender_creator || sender_power.filter(|&p| p < &power_levels.kick).is_some()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if sender == target_user {
|
||||
let allow = target_user_current_membership == MembershipState::Join
|
||||
|| target_user_current_membership == MembershipState::Invite
|
||||
|| target_user_current_membership == MembershipState::Knock;
|
||||
// self-leave
|
||||
// let allow = target_user_current_membership == MembershipState::Join
|
||||
// || target_user_current_membership == MembershipState::Invite
|
||||
// || target_user_current_membership == MembershipState::Knock;
|
||||
let allow = matches!(
|
||||
target_user_current_membership,
|
||||
MembershipState::Join | MembershipState::Invite | MembershipState::Knock
|
||||
);
|
||||
if !allow {
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
?target_user_current_membership,
|
||||
"Can't leave if sender is not already invited, knocked, or joined"
|
||||
%sender,
|
||||
current_membership_event_id=?target_user_membership_event_id,
|
||||
current_membership=?target_user_current_membership,
|
||||
"sender cannot leave as they are not already knocking on, invited to, or joined to the room"
|
||||
);
|
||||
}
|
||||
trace!(sender=%sender, "allowing leave");
|
||||
allow
|
||||
} else if !sender_is_joined
|
||||
|| target_user_current_membership == MembershipState::Ban
|
||||
&& (sender_creator
|
||||
|| sender_power.filter(|&p| p < &power_levels.ban).is_some())
|
||||
{
|
||||
} else if !sender_is_joined {
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
%sender,
|
||||
?sender_membership_event_id,
|
||||
"Can't kick if sender not joined or user is already banned",
|
||||
"sender cannot kick another user as they are not joined to the room",
|
||||
);
|
||||
false
|
||||
} else if !can_unban {
|
||||
// If the target is banned, only a room creator or someone with ban power
|
||||
// level can unban them
|
||||
warn!(
|
||||
%sender,
|
||||
?target_user_membership_event_id,
|
||||
?power_levels_event_id,
|
||||
"sender lacks the power level required to unban users",
|
||||
);
|
||||
false
|
||||
} else if !can_kick {
|
||||
warn!(
|
||||
%sender,
|
||||
%target_user,
|
||||
?target_user_membership_event_id,
|
||||
?target_user_current_membership,
|
||||
?power_levels_event_id,
|
||||
"sender does not have enough power to kick the target",
|
||||
);
|
||||
false
|
||||
} else {
|
||||
let allow = sender_creator
|
||||
|| (sender_power.filter(|&p| p >= &power_levels.kick).is_some()
|
||||
&& target_power < sender_power);
|
||||
if !allow {
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
?power_levels_event_id,
|
||||
"User does not have enough power to kick",
|
||||
);
|
||||
}
|
||||
allow
|
||||
},
|
||||
trace!(
|
||||
%sender,
|
||||
%target_user,
|
||||
?target_user_membership_event_id,
|
||||
?target_user_current_membership,
|
||||
sender_pl=?sender_power,
|
||||
target_pl=?target_power,
|
||||
required_pl=?power_levels.kick,
|
||||
"allowing kick/unban",
|
||||
);
|
||||
true
|
||||
}
|
||||
},
|
||||
| MembershipState::Ban =>
|
||||
if !sender_is_joined {
|
||||
warn!(?sender_membership_event_id, "Can't ban user if sender is not joined");
|
||||
warn!(
|
||||
%sender,
|
||||
?sender_membership_event_id,
|
||||
"sender cannot ban another user as they are not joined to the room",
|
||||
);
|
||||
false
|
||||
} else {
|
||||
let allow = sender_creator
|
||||
@@ -949,9 +1071,11 @@ struct GetThirdPartyInvite {
|
||||
&& target_power < sender_power);
|
||||
if !allow {
|
||||
warn!(
|
||||
%sender,
|
||||
%target_user,
|
||||
?target_user_membership_event_id,
|
||||
?power_levels_event_id,
|
||||
"User does not have enough power to ban",
|
||||
"sender does not have enough power to ban the target",
|
||||
);
|
||||
}
|
||||
allow
|
||||
@@ -977,9 +1101,9 @@ struct GetThirdPartyInvite {
|
||||
} else if sender != target_user {
|
||||
// 3. If `sender` does not match `state_key`, reject.
|
||||
warn!(
|
||||
?sender,
|
||||
?target_user,
|
||||
"Can't make another user knock, sender did not match target"
|
||||
%sender,
|
||||
%target_user,
|
||||
"sender cannot knock on behalf of another user",
|
||||
);
|
||||
false
|
||||
} else if matches!(
|
||||
@@ -991,15 +1115,25 @@ struct GetThirdPartyInvite {
|
||||
// 5. Otherwise, reject.
|
||||
warn!(
|
||||
?target_user_membership_event_id,
|
||||
?sender_membership,
|
||||
"Knocking with a membership state of ban, invite or join is invalid",
|
||||
);
|
||||
false
|
||||
} else {
|
||||
trace!(%sender, "allowing knock");
|
||||
true
|
||||
}
|
||||
},
|
||||
| _ => {
|
||||
warn!("Unknown membership transition");
|
||||
warn!(
|
||||
%sender,
|
||||
?target_membership,
|
||||
%target_user,
|
||||
%target_user_current_membership,
|
||||
"Unknown or invalid membership transition {} -> {}",
|
||||
target_user_current_membership,
|
||||
target_membership
|
||||
);
|
||||
false
|
||||
},
|
||||
})
|
||||
@@ -1029,6 +1163,13 @@ fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int)
|
||||
if event.state_key().is_some_and(|k| k.starts_with('@'))
|
||||
&& event.state_key() != Some(event.sender().as_str())
|
||||
{
|
||||
warn!(
|
||||
%user_level,
|
||||
required=?event_type_power_level,
|
||||
state_key=?event.state_key(),
|
||||
sender=%event.sender(),
|
||||
"state_key starts with @ but does not match sender",
|
||||
);
|
||||
return false; // permission required to post in this room
|
||||
}
|
||||
|
||||
@@ -1113,7 +1254,14 @@ fn check_power_levels(
|
||||
|
||||
// If the current value is equal to the sender's current power level, reject
|
||||
if user != power_event.sender() && old_level == Some(&user_level) {
|
||||
warn!("m.room.power_level cannot remove ops == to own");
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
?user,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot alter the power level of a user with the same power level as sender's own"
|
||||
);
|
||||
return Some(false); // cannot remove ops level == to own
|
||||
}
|
||||
|
||||
@@ -1121,8 +1269,26 @@ fn check_power_levels(
|
||||
// If the new value is higher than the sender's current power level, reject
|
||||
let old_level_too_big = old_level > Some(&user_level);
|
||||
let new_level_too_big = new_level > Some(&user_level);
|
||||
if old_level_too_big || new_level_too_big {
|
||||
warn!("m.room.power_level failed to add ops > than own");
|
||||
if old_level_too_big {
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
?user,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot alter the power level of a user with a higher power level than sender's own"
|
||||
);
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
if new_level_too_big {
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
?user,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot set the power level of a user to a level higher than sender's own"
|
||||
);
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
@@ -1139,8 +1305,26 @@ fn check_power_levels(
|
||||
// If the new value is higher than the sender's current power level, reject
|
||||
let old_level_too_big = old_level > Some(&user_level);
|
||||
let new_level_too_big = new_level > Some(&user_level);
|
||||
if old_level_too_big || new_level_too_big {
|
||||
warn!("m.room.power_level failed to add ops > than own");
|
||||
if old_level_too_big {
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
?ev_type,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot alter the power level of an event with a higher power level than sender's own"
|
||||
);
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
if new_level_too_big {
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
?ev_type,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot set the power level of an event to a level higher than sender's own"
|
||||
);
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
@@ -1155,7 +1339,13 @@ fn check_power_levels(
|
||||
let old_level_too_big = old_level > user_level;
|
||||
let new_level_too_big = new_level > user_level;
|
||||
if old_level_too_big || new_level_too_big {
|
||||
warn!("m.room.power_level failed to add ops > than own");
|
||||
warn!(
|
||||
?old_level,
|
||||
?new_level,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
"cannot alter the power level of notifications greater than sender's own"
|
||||
);
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
@@ -1179,7 +1369,14 @@ fn check_power_levels(
|
||||
let new_level_too_big = new_lvl > user_level;
|
||||
|
||||
if old_level_too_big || new_level_too_big {
|
||||
warn!("cannot add ops > than own");
|
||||
warn!(
|
||||
?old_lvl,
|
||||
?new_lvl,
|
||||
%user_level,
|
||||
sender=%power_event.sender(),
|
||||
action=%lvl_name,
|
||||
"cannot alter the power level of action greater than sender's own",
|
||||
);
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
room_version::RoomVersion,
|
||||
};
|
||||
use crate::{
|
||||
debug, debug_error,
|
||||
debug, debug_error, err,
|
||||
matrix::{Event, StateKey},
|
||||
state_res::room_version::StateResolutionVersion,
|
||||
trace,
|
||||
@@ -319,8 +319,19 @@ async fn calculate_conflicted_subgraph<F, Fut, E>(
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
let evt = fetch_event(event_id.clone()).await?;
|
||||
stack.push(evt.auth_events().map(ToOwned::to_owned).collect());
|
||||
trace!(event_id = event_id.as_str(), "fetching event for its auth events");
|
||||
let evt = fetch_event(event_id.clone()).await;
|
||||
if evt.is_none() {
|
||||
err!("could not fetch event {} to calculate conflicted subgraph", event_id);
|
||||
path.pop();
|
||||
continue;
|
||||
}
|
||||
stack.push(
|
||||
evt.expect("checked")
|
||||
.auth_events()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect(),
|
||||
);
|
||||
seen.insert(event_id);
|
||||
}
|
||||
Some(subgraph)
|
||||
|
||||
@@ -156,6 +156,7 @@ sentry_telemetry = [
|
||||
]
|
||||
systemd = [
|
||||
"conduwuit-router/systemd",
|
||||
"conduwuit-service/systemd"
|
||||
]
|
||||
journald = [ # This is a stub on non-unix platforms
|
||||
"dep:tracing-journald",
|
||||
|
||||
@@ -40,7 +40,6 @@ io_uring = [
|
||||
"conduwuit-admin/io_uring",
|
||||
"conduwuit-api/io_uring",
|
||||
"conduwuit-service/io_uring",
|
||||
"conduwuit-api/io_uring",
|
||||
]
|
||||
jemalloc = [
|
||||
"conduwuit-admin/jemalloc",
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) async fn start(server: Arc<Server>) -> Result<Arc<Services>> {
|
||||
let services = Services::build(server).await?.start().await?;
|
||||
|
||||
#[cfg(all(feature = "systemd", target_os = "linux"))]
|
||||
sd_notify::notify(true, &[sd_notify::NotifyState::Ready])
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])
|
||||
.expect("failed to notify systemd of ready state");
|
||||
|
||||
debug!("Started");
|
||||
@@ -78,7 +78,7 @@ pub(crate) async fn stop(services: Arc<Services>) -> Result<()> {
|
||||
debug!("Shutting down...");
|
||||
|
||||
#[cfg(all(feature = "systemd", target_os = "linux"))]
|
||||
sd_notify::notify(true, &[sd_notify::NotifyState::Stopping])
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])
|
||||
.expect("failed to notify systemd of stopping state");
|
||||
|
||||
// Wait for all completions before dropping or we'll lose them to the module
|
||||
|
||||
@@ -67,6 +67,9 @@ release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
]
|
||||
systemd = [
|
||||
"dep:sd-notify",
|
||||
]
|
||||
url_preview = [
|
||||
"dep:image",
|
||||
"dep:webpage",
|
||||
@@ -119,5 +122,9 @@ blurhash.optional = true
|
||||
recaptcha-verify = { version = "0.1.5", default-features = false }
|
||||
ctor.workspace = true
|
||||
|
||||
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
||||
sd-notify.workspace = true
|
||||
sd-notify.optional = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -45,13 +45,16 @@ fn deref(&self) -> &Self::Target { &self.server.config }
|
||||
fn handle_reload(&self) -> Result {
|
||||
if self.server.config.config_reload_signal {
|
||||
#[cfg(all(feature = "systemd", target_os = "linux"))]
|
||||
sd_notify::notify(true, &[sd_notify::NotifyState::Reloading])
|
||||
.expect("failed to notify systemd of reloading state");
|
||||
sd_notify::notify(false, &[
|
||||
sd_notify::NotifyState::Reloading,
|
||||
sd_notify::NotifyState::monotonic_usec_now().expect("Failed to read monotonic time"),
|
||||
])
|
||||
.expect("failed to notify systemd of reloading state");
|
||||
|
||||
self.reload(iter::empty())?;
|
||||
|
||||
#[cfg(all(feature = "systemd", target_os = "linux"))]
|
||||
sd_notify::notify(true, &[sd_notify::NotifyState::Ready])
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])
|
||||
.expect("failed to notify systemd of ready state");
|
||||
}
|
||||
|
||||
|
||||
@@ -90,17 +90,22 @@ pub async fn create(
|
||||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
// Width, Height = 0 if it's not a thumbnail
|
||||
let key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
user,
|
||||
&Dim::default(),
|
||||
content_disposition,
|
||||
content_type,
|
||||
)?;
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, user, &Dim::default(), content_disposition, content_type)
|
||||
.map_err(|e| {
|
||||
err!(Database(error!("Failed to create media metadata for MXC {mxc}: {e}")))
|
||||
})?;
|
||||
|
||||
//TODO: Dangling metadata in database if creation fails
|
||||
let mut f = self.create_media_file(&key).await?;
|
||||
f.write_all(file).await?;
|
||||
let mut f = self.create_media_file(&key).await.map_err(|e| {
|
||||
err!(Database(error!(
|
||||
"Failed to create media file for MXC {mxc} at key {key:?}: {e}"
|
||||
)))
|
||||
})?;
|
||||
f.write_all(file).await.map_err(|e| {
|
||||
err!(Database(error!("Failed to write media file for MXC {mxc} at key {key:?}: {e}")))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
},
|
||||
warn,
|
||||
};
|
||||
use database::Json;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
use ruma::{
|
||||
@@ -606,7 +607,7 @@ async fn fix_corrupt_msc4133_fields(services: &Services) -> Result {
|
||||
);
|
||||
};
|
||||
|
||||
useridprofilekey_value.put((user, key), new_value);
|
||||
useridprofilekey_value.put((user, key), Json(new_value));
|
||||
fixed = fixed.saturating_add(1);
|
||||
}
|
||||
total = total.saturating_add(1);
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
};
|
||||
|
||||
use conduwuit::{
|
||||
Event, PduEvent, debug, debug_error, debug_warn, implement,
|
||||
matrix::event::gen_event_id_canonical_json, trace, utils::continue_exponential_backoff_secs,
|
||||
warn,
|
||||
Event, PduEvent, debug, debug_warn, implement, matrix::event::gen_event_id_canonical_json,
|
||||
trace, utils::continue_exponential_backoff_secs, warn,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName,
|
||||
@@ -52,12 +51,14 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
};
|
||||
|
||||
let mut events_with_auth_events = Vec::with_capacity(events.clone().count());
|
||||
trace!("Fetching {} outlier pdus", events.clone().count());
|
||||
|
||||
for id in events {
|
||||
// a. Look in the main timeline (pduid_pdu tree)
|
||||
// b. Look at outlier pdu tree
|
||||
// (get_pdu_json checks both)
|
||||
if let Ok(local_pdu) = self.services.timeline.get_pdu(id).await {
|
||||
trace!("Found {id} in main timeline or outlier tree");
|
||||
events_with_auth_events.push((id.to_owned(), Some(local_pdu), vec![]));
|
||||
continue;
|
||||
}
|
||||
@@ -104,7 +105,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("Fetching {next_id} over federation.");
|
||||
debug!("Fetching {next_id} over federation from {origin}.");
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
@@ -115,7 +116,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
.await
|
||||
{
|
||||
| Ok(res) => {
|
||||
debug!("Got {next_id} over federation");
|
||||
debug!("Got {next_id} over federation from {origin}");
|
||||
let Ok(room_version_id) = get_room_version_id(create_event) else {
|
||||
back_off((*next_id).to_owned());
|
||||
continue;
|
||||
@@ -145,6 +146,9 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
auth_event.clone().into(),
|
||||
) {
|
||||
| Ok(auth_event) => {
|
||||
trace!(
|
||||
"Found auth event id {auth_event} for event {next_id}"
|
||||
);
|
||||
todo_auth_events.push_back(auth_event);
|
||||
},
|
||||
| _ => {
|
||||
@@ -160,7 +164,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
events_all.insert(next_id);
|
||||
},
|
||||
| Err(e) => {
|
||||
debug_error!("Failed to fetch event {next_id}: {e}");
|
||||
warn!("Failed to fetch auth event {next_id} from {origin}: {e}");
|
||||
back_off((*next_id).to_owned());
|
||||
},
|
||||
}
|
||||
@@ -175,7 +179,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
// b. Look at outlier pdu tree
|
||||
// (get_pdu_json checks both)
|
||||
if let Some(local_pdu) = local_pdu {
|
||||
trace!("Found {id} in db");
|
||||
trace!("Found {id} in main timeline or outlier tree");
|
||||
pdus.push((local_pdu.clone(), None));
|
||||
}
|
||||
|
||||
@@ -201,6 +205,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Handling outlier {next_id}");
|
||||
match Box::pin(self.handle_outlier_pdu(
|
||||
origin,
|
||||
create_event,
|
||||
@@ -213,6 +218,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
{
|
||||
| Ok((pdu, json)) =>
|
||||
if next_id == *id {
|
||||
trace!("Handled outlier {next_id} (original request)");
|
||||
pdus.push((pdu, Some(json)));
|
||||
},
|
||||
| Err(e) => {
|
||||
@@ -222,6 +228,6 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Fetched and handled {} outlier pdus", pdus.len());
|
||||
pdus
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::collections::{BTreeMap, HashMap, hash_map};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Event, PduEvent, Result, debug, debug_info, err, implement, state_res, trace, warn,
|
||||
Err, Event, PduEvent, Result, debug, debug_info, debug_warn, err, implement, state_res, trace,
|
||||
};
|
||||
use futures::future::ready;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, ServerName, events::StateEventType,
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName,
|
||||
events::StateEventType,
|
||||
};
|
||||
|
||||
use super::{check_room_id, get_room_version_id, to_room_version};
|
||||
@@ -74,36 +75,73 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
|
||||
|
||||
check_room_id(room_id, &pdu_event)?;
|
||||
|
||||
if !auth_events_known {
|
||||
// 4. fetch any missing auth events doing all checks listed here starting at 1.
|
||||
// These are not timeline events
|
||||
// 5. Reject "due to auth events" if can't get all the auth events or some of
|
||||
// the auth events are also rejected "due to auth events"
|
||||
// NOTE: Step 5 is not applied anymore because it failed too often
|
||||
debug!("Fetching auth events");
|
||||
Box::pin(self.fetch_and_handle_outliers(
|
||||
origin,
|
||||
pdu_event.auth_events(),
|
||||
create_event,
|
||||
room_id,
|
||||
))
|
||||
.await;
|
||||
// Fetch all auth events
|
||||
let mut auth_events: HashMap<OwnedEventId, PduEvent> = HashMap::new();
|
||||
|
||||
for aid in pdu_event.auth_events() {
|
||||
if let Ok(auth_event) = self.services.timeline.get_pdu(aid).await {
|
||||
check_room_id(room_id, &auth_event)?;
|
||||
trace!("Found auth event {aid} for outlier event {event_id} locally");
|
||||
auth_events.insert(aid.to_owned(), auth_event);
|
||||
} else {
|
||||
debug_warn!("Could not find auth event {aid} for outlier event {event_id} locally");
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch any missing ones & reject invalid ones
|
||||
let missing_auth_events = if auth_events_known {
|
||||
pdu_event
|
||||
.auth_events()
|
||||
.filter(|id| !auth_events.contains_key(*id))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
pdu_event.auth_events().collect::<Vec<_>>()
|
||||
};
|
||||
if !missing_auth_events.is_empty() || !auth_events_known {
|
||||
debug_info!(
|
||||
"Fetching {} missing auth events for outlier event {event_id}",
|
||||
missing_auth_events.len()
|
||||
);
|
||||
for (pdu, _) in self
|
||||
.fetch_and_handle_outliers(
|
||||
origin,
|
||||
missing_auth_events.iter().copied(),
|
||||
create_event,
|
||||
room_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
auth_events.insert(pdu.event_id().to_owned(), pdu);
|
||||
}
|
||||
} else {
|
||||
debug!("No missing auth events for outlier event {event_id}");
|
||||
}
|
||||
// reject if we are still missing some
|
||||
let still_missing = pdu_event
|
||||
.auth_events()
|
||||
.filter(|id| !auth_events.contains_key(*id))
|
||||
.collect::<Vec<_>>();
|
||||
if !still_missing.is_empty() {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Could not fetch all auth events for outlier event {event_id}, still missing: \
|
||||
{still_missing:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
// 6. Reject "due to auth events" if the event doesn't pass auth based on the
|
||||
// auth events
|
||||
debug!("Checking based on auth events");
|
||||
let mut auth_events_by_key: HashMap<_, _> = HashMap::with_capacity(auth_events.len());
|
||||
// Build map of auth events
|
||||
let mut auth_events = HashMap::with_capacity(pdu_event.auth_events().count());
|
||||
for id in pdu_event.auth_events() {
|
||||
let Ok(auth_event) = self.services.timeline.get_pdu(id).await else {
|
||||
warn!("Could not find auth event {id}");
|
||||
continue;
|
||||
};
|
||||
let auth_event = auth_events
|
||||
.get(id)
|
||||
.expect("we just checked that we have all auth events")
|
||||
.to_owned();
|
||||
|
||||
check_room_id(room_id, &auth_event)?;
|
||||
|
||||
match auth_events.entry((
|
||||
match auth_events_by_key.entry((
|
||||
auth_event.kind.to_string().into(),
|
||||
auth_event
|
||||
.state_key
|
||||
@@ -123,7 +161,7 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
|
||||
|
||||
// The original create event must be in the auth events
|
||||
if !matches!(
|
||||
auth_events.get(&(StateEventType::RoomCreate, String::new().into())),
|
||||
auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())),
|
||||
Some(_) | None
|
||||
) {
|
||||
return Err!(Request(InvalidParam("Incoming event refers to wrong create event.")));
|
||||
@@ -131,7 +169,7 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
|
||||
|
||||
let state_fetch = |ty: &StateEventType, sk: &str| {
|
||||
let key = (ty.to_owned(), sk.into());
|
||||
ready(auth_events.get(&key).map(ToOwned::to_owned))
|
||||
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
|
||||
};
|
||||
|
||||
let auth_check = state_res::event_auth::auth_check(
|
||||
|
||||
@@ -274,8 +274,6 @@ fn from_evt(
|
||||
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
|
||||
|
||||
// Check with the policy server
|
||||
// TODO(hydra): Skip this check for create events (why didnt we do this
|
||||
// already?)
|
||||
if room_id.is_some() {
|
||||
trace!(
|
||||
"Checking event {} in room {} with policy server",
|
||||
|
||||
Reference in New Issue
Block a user