Compare commits

..

100 Commits

Author SHA1 Message Date
strawberry af77615e8a implement MSC4166, returning M_NOT_FOUND if no TURN URIs are set
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-30 00:11:16 -04:00
strawberry f91e40bc44 rust: bump console-subscriber to 0.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-29 19:18:56 -04:00
strawberry 111f2223bd nix: use cortex-a73 / ARMv8-a, strip and remove unnecessary bloat
for some reason gcc is included in OCI images

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-29 19:11:02 -04:00
strawberry 1d97858aa6 add support for MSC4168: migrate m.room.* on room upgrades
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-29 01:04:26 -04:00
strawberry 2834d50ddf display success/failure count in get-remote-pdu-list
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 21:38:14 -04:00
strawberry 3c9b702527 docs: mention FreeBSD specific build commandline for deployment
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 21:34:08 -04:00
strawberry a8c707d8a5 docs: update configuration docs for the new -O option
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 16:46:20 -04:00
strawberry 85c5b11e73 bump tokio, use default-features = false for more crates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 15:15:39 -04:00
strawberry e0d3180303 docs: mention potential DNS issues when using docker
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry 1e059d209c dont consider presence status changes apart of presence spam
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry 8e690f73d6 debian: remove --verbose from postinst
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry 547bc2171c reduce unnecessary logging on URL preview and event, use sensible error code for URL previews
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry 275075a2f5 apply forbidden_remote_server_names to outbound sending and inbound federation handling
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry 335be12f79 set last_seen_ip on new/initial device creation
this is not automatically updating, but at least have something
useful there instead of nothing

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
strawberry fd74a0ac3d sync: always include_redundant_members when using element_hacks feature
see:
- https://github.com/matrix-org/matrix-js-sdk/pull/4329
- https://github.com/element-hq/synapse/blob/568051c0f07393b786b9d813a1db53dd332c9fc2/synapse/handlers/pagination.py#L639
- https://matrix.to/#/!n8DKU1BeeJilOJXDPr:seirdy.one/$gvGF7GuPrLPR8nHg4UiHvP-ZVwuiMkYS8O6VlsDCW9w?via=awawawawawawawawawawawawawawawawawawawawawawawawawawawawawawaw.gay&via=transfem.dev&via=matrix.org

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 12:53:45 -04:00
Jason Volk 5c258f41c8 fixes for modules
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk 15126ee1b2 additional weak references where applicable
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk e37ac56dba use weak references to break services dependency cycle
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk a74613be96 improve database repair/shutdown log messages
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk a95ded167f fix type complexity
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk 95121b6e05 use single-line codeblock for specific features lists
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk e6884ef13a fix various nightly incidental lints
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk 49033db2b8 fix trailing body passed to commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk ddc8c3b46c split presence data object into file; improve service encapsulations
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
Jason Volk ea95627dce hoist rustc flag string array to global const
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 05:59:12 -07:00
strawberry c3e751542d ci: default to empty string to "unset" variables
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-28 05:59:12 -07:00
strawberry 4b765aa17c ci: only use sccache conditionally
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-27 15:28:17 -04:00
Jason Volk 954cfc6bb7 bump cargo
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 09:08:51 +00:00
Jason Volk 2f85a5c1ac de-global services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 09:08:51 +00:00
Jason Volk 7e50db4193 de-global services from admin
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 7a3cc3941e parse generics for implement macro
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk ca82b59c6f upgrade to syn 2.x
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 3b5607ecdc add macro for out-of-line definitions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 96f6a75bc8 add refutable pattern function macro
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 68f42baf73 rename admin Command to CommandInput
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 271959ee27 add debug list-dependencies admin command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk c423a83656 add cli override for any configuration item
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 4e975887cf add command to list features
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 8bb69eb81d add simple ast dimension diagnostic
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 2100618d47 add rustc build flags reflection
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 936d2915e2 add cargo manifest reflection
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk f014231644 add conf item to disable rocksdb compaction
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 7d487d53d8 de-cycle conduit_macros from conduit_core.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk d7d874f88d start core info module; move version to info
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk ee864bcd9e normalize admin debug command handlers
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 111cbea6fc add debug time command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 85f734ec74 proc macro
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 2468e0c3de unconditionally derive Debug on subcommand enums
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk f841c2356d preliminary get-signing-keys command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk c64adbec0e split signing_keys_for from verify_keys_for
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 4458efa2b2 rename signing_keys_for to verify_keys_for
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 5c0bf29122 add util for camel to snake case conversion
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 91b49a7786 add basic exchange util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 5a17fbccf5 add type_name debug tool
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 5ed95ea357 contextualize handler in object
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk ccfa939bd3 split admin command enum from handler
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 263e338088 add --console program option to automatically spawn
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 2fb43dd38d infra to synthesize program options with config options
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 59efabbbc2 de-global server_is_ours / user_is_local
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 010e4ee35a de-global services for services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 992c0a1e58 de-global services for admin
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 9b20c6918f add indirection for circular-dependencies between services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 3dc91525ce fix over-tabulation
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk a88f913a17 enrich interface for inter-service referencing
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk 4343218957 initialize some containers with_capacity
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:42 +00:00
Jason Volk b116984e46 use mutex_map for url preview lock
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:41 +00:00
Jason Volk b0ac5255c8 move sending service impl properly back to mod root
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:41 +00:00
Jason Volk 29fc5b9b52 de-global some services in services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:41 +00:00
Jason Volk 3ccd9ea326 consolidate all resolution in resolver; split units
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 08:26:41 +00:00
Jason Volk 8a2ae401df convert Client into Service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 07:54:05 +00:00
Jason Volk f465d77ad3 convert Resolver into a Service.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 07:54:05 +00:00
Jason Volk 2fd6f6b0ff add polymorphism to Services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 07:54:05 +00:00
Jason Volk 8b6018d77d de-global services() from api
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 07:54:03 +00:00
Jason Volk 463f1a1287 only use graceful shutdown w/ axum-server fixed
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-27 07:53:09 +00:00
strawberry 04fc87ad07 ci: try to free more runner storage/space
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry dc573f4562 ci: run free disk space for documentation workflow too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 74826dcb94 resolve lint_groups_priority lint (https://github.com/rust-lang/cargo/issues/12918)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry b20c4e0539 bump MSRV and rust toolchain to 1.80.0
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry fb8a2846df ci/nix: use a "test" cargo profile
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 04971d0430 ci: cache all crates in rust-cache
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry f098532b09 ci: retry nix-build-and-cache 3 times before failing due to attic issues
attic just sucks and every 5 CI runs guarantees at least 2 false
gateway errors that i can't do anything about.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 60927c1c72 ci: try using sccache
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 03296412ab ci: validate deb packages produced
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 934ab6a4fe nix: add dpkg to devshell packages for dpkg-deb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:10 -04:00
strawberry 1378399f9d ci: build and upload static debug/dev binaries and OCI images
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-25 22:50:08 -04:00
strawberry 09680f653f nix: add outputs for debug builds
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 19:55:49 -04:00
strawberry 39f19c483a nix: use all-features for complement OCI images
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 19:55:46 -04:00
strawberry 39c0f4ea3d delete unused bad_signature_ratelimiter rm'd in 67f9553790
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 19:12:20 -04:00
strawberry 405167fc3f add harmless check for presence PUT matching sender user
this is already done but we just don't error and always
use the sender user. match synapse behaviour where we check
and error.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry b13ea4ff45 fix non-working admin room topic accessor output
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry 100fc5e0f7 bump tokio to 1.39.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry 96f9d64111 add back is_historical check on admin user creations from accidental removal
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry 30ad5da5f1 use ruma-identifiers-validation feature from our ruma
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry ce0ca96df4 clarify that sentry_traces_sample_rate is a percentage
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry 4851ad26e2 bump cargo.lock, lock jemallocator to 0.5.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:25 -04:00
strawberry 328759a60f ci: run quick smoke test of x86_64 static binary
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-24 01:41:21 -04:00
strawberry 463fa4fd53 nix: dont build unnecessary jemalloc docs or cxx integration
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-22 17:57:51 -04:00
strawberry db494e0d68 bump conduwuit to 0.4.6
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-22 17:57:51 -04:00
Myned 4788040458 fix media_startup_check typo
Signed-off-by: Myned <dev@bjork.tech>
2024-07-22 13:31:20 -04:00
matt 06531993f6 Calculate Age Rather than Hardcoding to 1 (#531) 2024-07-17 21:47:59 -04:00
230 changed files with 8700 additions and 5808 deletions
+243 -6
View File
@@ -27,6 +27,16 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
# sccache only on main repo
SCCACHE_GHA_ENABLED: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
RUSTC_WRAPPER: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
SCCACHE_BUCKET: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
SCCACHE_S3_USE_SSL: ${{ vars.SCCACHE_S3_USE_SSL }}
SCCACHE_REGION: ${{ vars.SCCACHE_REGION }}
SCCACHE_ENDPOINT: ${{ vars.SCCACHE_ENDPOINT }}
SCCACHE_CACHE_MULTIARCH: ${{ vars.SCCACHE_CACHE_MULTIARCH }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# Required to make some things output color # Required to make some things output color
TERM: ansi TERM: ansi
# Publishing to my nix binary cache # Publishing to my nix binary cache
@@ -52,6 +62,8 @@ jobs:
tests: tests:
name: Test name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_PROFILE: "test"
steps: steps:
- name: Free Disk Space (Ubuntu) - name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main uses: jlumbroso/free-disk-space@main
@@ -124,9 +136,33 @@ jobs:
- name: Cache CI dependencies - name: Cache CI dependencies
run: | run: |
bin/nix-build-and-cache ci # attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache ci
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
- name: Run CI tests - name: Run CI tests
run: | run: |
@@ -180,6 +216,10 @@ jobs:
echo '```' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
fi fi
- name: Run cargo clean test artifacts
run: |
cargo clean --profile test
build: build:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -190,6 +230,9 @@ jobs:
- target: aarch64-unknown-linux-musl - target: aarch64-unknown-linux-musl
- target: x86_64-unknown-linux-musl - target: x86_64-unknown-linux-musl
steps: steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
- name: Sync repository - name: Sync repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -244,12 +287,39 @@ jobs:
direnv allow direnv allow
nix develop .#all-features --command true nix develop .#all-features --command true
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
- name: Build static ${{ matrix.target }} - name: Build static ${{ matrix.target }}
run: | run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*') CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features # attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
mkdir -v -p target/release/ mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/ mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduit target/release/conduwuit cp -v -f result/bin/conduit target/release/conduwuit
@@ -259,6 +329,66 @@ jobs:
mv -v target/release/conduwuit static-${{ matrix.target }} mv -v target/release/conduwuit static-${{ matrix.target }}
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
# quick smoke test of the x86_64 static release binary
- name: Run x86_64 static release binary
run: |
# GH actions default runners are x86_64 only
if file result/bin/conduit | grep x86-64; then
result/bin/conduit --version
fi
- name: Build static debug ${{ matrix.target }}
run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
# > warning: dev profile is not supported and will be a hard error in the future. cargo-deb is for making releases, and it doesn't make sense to use it with dev profiles.
# so we need to coerce cargo-deb into thinking this is a release binary
mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduit target/release/conduwuit
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
# -p conduit is the main crate name
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}-debug.deb
mv -v target/release/conduwuit static-${{ matrix.target }}-debug
mv -v target/release/${{ matrix.target }}-debug.deb ${{ matrix.target }}-debug.deb
# quick smoke test of the x86_64 static debug binary
- name: Run x86_64 static debug binary
run: |
# GH actions default runners are x86_64 only
if file result/bin/conduit | grep x86-64; then
result/bin/conduit --version
fi
# check validity of produced deb package, invalid debs will error on these commands
- name: Validate produced deb package
run: |
# List contents
dpkg-deb --contents ${{ matrix.target }}.deb
dpkg-deb --contents ${{ matrix.target }}-debug.deb
# List info
dpkg-deb --info ${{ matrix.target }}.deb
dpkg-deb --info ${{ matrix.target }}-debug.deb
- name: Upload static-${{ matrix.target }} - name: Upload static-${{ matrix.target }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -274,11 +404,65 @@ jobs:
if-no-files-found: error if-no-files-found: error
compression-level: 0 compression-level: 0
- name: Upload static-${{ matrix.target }}-debug
uses: actions/upload-artifact@v4
with:
name: static-${{ matrix.target }}-debug
path: static-${{ matrix.target }}-debug
if-no-files-found: error
- name: Upload deb ${{ matrix.target }}-debug
uses: actions/upload-artifact@v4
with:
name: deb-${{ matrix.target }}-debug
path: ${{ matrix.target }}-debug.deb
if-no-files-found: error
compression-level: 0
- name: Build OCI image ${{ matrix.target }} - name: Build OCI image ${{ matrix.target }}
run: | run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features # attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
cp -v -f result oci-image-${{ matrix.target }}.tar.gz cp -v -f result oci-image-${{ matrix.target }}.tar.gz
- name: Build debug OCI image ${{ matrix.target }}
run: |
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
- name: Upload OCI image ${{ matrix.target }} - name: Upload OCI image ${{ matrix.target }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -287,6 +471,14 @@ jobs:
if-no-files-found: error if-no-files-found: error
compression-level: 0 compression-level: 0
- name: Upload OCI image ${{ matrix.target }}-debug
uses: actions/upload-artifact@v4
with:
name: oci-image-${{ matrix.target }}-debug
path: oci-image-${{ matrix.target }}-debug.tar.gz
if-no-files-found: error
compression-level: 0
docker: docker:
name: Docker publish name: Docker publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -337,8 +529,10 @@ jobs:
- name: Move OCI images into position - name: Move OCI images into position
run: | run: |
mv -v oci-image-x86_64-*/*.tar.gz oci-image-amd64.tar.gz mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.tar.gz
mv -v oci-image-aarch64-*/*.tar.gz oci-image-arm64v8.tar.gz mv -v oci-image-aarch64-unknown-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
mv -v oci-image-x86_64-unknown-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
mv -v oci-image-aarch64-unknown-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
- name: Load and push amd64 image - name: Load and push amd64 image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }} if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
@@ -362,6 +556,28 @@ jobs:
docker push ${{ env.GHCR_ARM64 }} docker push ${{ env.GHCR_ARM64 }}
docker push ${{ env.GLCR_ARM64 }} docker push ${{ env.GLCR_ARM64 }}
- name: Load and push amd64 debug image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-amd64-debug.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}-debug
docker push ${{ env.DOCKER_AMD64 }}-debug
docker push ${{ env.GHCR_AMD64 }}-debug
docker push ${{ env.GLCR_AMD64 }}-debug
- name: Load and push arm64 debug image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-arm64v8-debug.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}-debug
docker push ${{ env.DOCKER_ARM64 }}-debug
docker push ${{ env.GHCR_ARM64 }}-debug
docker push ${{ env.GLCR_ARM64 }}-debug
- name: Create Docker combined manifests - name: Create Docker combined manifests
run: | run: |
# Dockerhub Container Registry # Dockerhub Container Registry
@@ -371,9 +587,21 @@ jobs:
docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }} docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }} docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
# GitLab Container Registry # GitLab Container Registry
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GCCR_AMD64 }} docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }} docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
- name: Create Docker combined debug manifests
run: |
# Dockerhub Container Registry
docker manifest create ${{ env.DOCKER_TAG }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
docker manifest create ${{ env.DOCKER_BRANCH }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
# GitHub Container Registry
docker manifest create ${{ env.GHCR_TAG }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
docker manifest create ${{ env.GHCR_BRANCH }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
# GitLab Container Registry
docker manifest create ${{ env.GLCR_TAG }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
docker manifest create ${{ env.GLCR_BRANCH }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
- name: Push manifests to Docker registries - name: Push manifests to Docker registries
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }} if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: | run: |
@@ -383,6 +611,12 @@ jobs:
docker manifest push ${{ env.GHCR_BRANCH }} docker manifest push ${{ env.GHCR_BRANCH }}
docker manifest push ${{ env.GLCR_TAG }} docker manifest push ${{ env.GLCR_TAG }}
docker manifest push ${{ env.GLCR_BRANCH }} docker manifest push ${{ env.GLCR_BRANCH }}
docker manifest push ${{ env.DOCKER_TAG }}-debug
docker manifest push ${{ env.DOCKER_BRANCH }}-debug
docker manifest push ${{ env.GHCR_TAG }}-debug
docker manifest push ${{ env.GHCR_BRANCH }}-debug
docker manifest push ${{ env.GLCR_TAG }}-debug
docker manifest push ${{ env.GLCR_BRANCH }}-debug
- name: Add Image Links to Job Summary - name: Add Image Links to Job Summary
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }} if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
@@ -390,3 +624,6 @@ jobs:
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GHCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`docker pull ${{ env.GHCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GLCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`docker pull ${{ env.GLCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.DOCKER_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GHCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GLCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
+40 -3
View File
@@ -47,6 +47,9 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
- name: Sync repository - name: Sync repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -107,7 +110,23 @@ jobs:
- name: Cache CI dependencies - name: Cache CI dependencies
run: | run: |
bin/nix-build-and-cache ci # attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache ci
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
- name: Run lychee and markdownlint - name: Run lychee and markdownlint
run: | run: |
@@ -116,8 +135,26 @@ jobs:
- name: Build documentation (book) - name: Build documentation (book)
run: | run: |
./bin/nix-build-and-cache just .#book # attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
cp -r --dereference result public ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache just .#book
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
cp -r --dereference result public
- name: Upload generated documentation (book) as normal artifact - name: Upload generated documentation (book) as normal artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
+1
View File
@@ -39,6 +39,7 @@ The following binaries are used in [`engage.toml`][engage.toml]:
- [`cargo-deb`][cargo-deb] - [`cargo-deb`][cargo-deb]
- [`lychee`][lychee] - [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli] - [`markdownlint-cli`][markdownlint-cli]
- `dpkg`
### Matrix tests ### Matrix tests
Generated
+258 -322
View File
File diff suppressed because it is too large Load Diff
+75 -26
View File
@@ -14,13 +14,13 @@ categories = ["network-programming"]
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust" description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
edition = "2021" edition = "2021"
homepage = "https://conduwuit.puppyirl.gay/" homepage = "https://conduwuit.puppyirl.gay/"
keywords = ["chat", "matrix", "server"] keywords = ["chat", "matrix", "server", "uwu"]
license = "Apache-2.0" license = "Apache-2.0"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
readme = "README.md" readme = "README.md"
repository = "https://github.com/girlbossceo/conduwuit" repository = "https://github.com/girlbossceo/conduwuit"
rust-version = "1.77.0" rust-version = "1.80.0"
version = "0.4.5" version = "0.4.6"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduit" name = "conduit"
@@ -28,6 +28,19 @@ name = "conduit"
[workspace.dependencies.const-str] [workspace.dependencies.const-str]
version = "0.5.7" version = "0.5.7"
[workspace.dependencies.ctor]
version = "0.2.8"
[workspace.dependencies.cargo_toml]
version = "0.20"
default-features = false
features = ["features"]
[workspace.dependencies.toml]
version = "0.8.14"
default-features = false
features = ["parse"]
[workspace.dependencies.sanitize-filename] [workspace.dependencies.sanitize-filename]
version = "0.5.0" version = "0.5.0"
@@ -84,6 +97,7 @@ features = ["typed-header", "tracing"]
[workspace.dependencies.axum-server] [workspace.dependencies.axum-server]
version = "0.6.0" version = "0.6.0"
default-features = false
features = ["tls-rustls"] features = ["tls-rustls"]
[workspace.dependencies.axum-client-ip] [workspace.dependencies.axum-client-ip]
@@ -91,10 +105,12 @@ version = "0.6.0"
[workspace.dependencies.tower] [workspace.dependencies.tower]
version = "0.4.13" version = "0.4.13"
default-features = false
features = ["util"] features = ["util"]
[workspace.dependencies.tower-http] [workspace.dependencies.tower-http]
version = "0.5.2" version = "0.5.2"
default-features = false
features = [ features = [
"add-extension", "add-extension",
"cors", "cors",
@@ -117,10 +133,12 @@ features = [
[workspace.dependencies.serde] [workspace.dependencies.serde]
version = "1.0.204" version = "1.0.204"
default-features = false
features = ["rc"] features = ["rc"]
[workspace.dependencies.serde_json] [workspace.dependencies.serde_json]
version = "1.0.120" version = "1.0.121"
default-features = false
features = ["raw_value"] features = ["raw_value"]
# Used for appservice registration files # Used for appservice registration files
@@ -188,7 +206,8 @@ version = "0.3.30"
default-features = false default-features = false
[workspace.dependencies.tokio] [workspace.dependencies.tokio]
version = "1.38.0" version = "1.39.2"
default-features = false
features = [ features = [
"fs", "fs",
"net", "net",
@@ -204,11 +223,12 @@ features = [
version = "0.3.1" version = "0.3.1"
[workspace.dependencies.libloading] [workspace.dependencies.libloading]
version = "0.8.3" version = "0.8.5"
# Validating urls in config, was already a transitive dependency # Validating urls in config, was already a transitive dependency
[workspace.dependencies.url] [workspace.dependencies.url]
version = "2.5.0" version = "2.5.0"
default-features = false
features = ["serde"] features = ["serde"]
# standard date and time tools # standard date and time tools
@@ -218,7 +238,8 @@ features = ["alloc", "std"]
default-features = false default-features = false
[workspace.dependencies.hyper] [workspace.dependencies.hyper]
version = "1.4.0" version = "1.4.1"
default-features = false
features = [ features = [
"server", "server",
"http1", "http1",
@@ -227,6 +248,7 @@ features = [
[workspace.dependencies.hyper-util] [workspace.dependencies.hyper-util]
version = "0.1.6" version = "0.1.6"
default-features = false
features = [ features = [
"client", "client",
"server-auto", "server-auto",
@@ -238,11 +260,13 @@ features = [
# to support multiple variations of setting a config option # to support multiple variations of setting a config option
[workspace.dependencies.either] [workspace.dependencies.either]
version = "1.11.0" version = "1.11.0"
default-features = false
features = ["serde"] features = ["serde"]
# Used for reading the configuration from conduwuit.toml & environment variables # Used for reading the configuration from conduwuit.toml & environment variables
[workspace.dependencies.figment] [workspace.dependencies.figment]
version = "0.10.18" version = "0.10.18"
default-features = false
features = ["env", "toml"] features = ["env", "toml"]
[workspace.dependencies.hickory-resolver] [workspace.dependencies.hickory-resolver]
@@ -251,7 +275,7 @@ default-features = false
# Used for conduit::Error type # Used for conduit::Error type
[workspace.dependencies.thiserror] [workspace.dependencies.thiserror]
version = "1.0.62" version = "1.0.63"
# Used when hashing the state # Used when hashing the state
[workspace.dependencies.ring] [workspace.dependencies.ring]
@@ -280,7 +304,7 @@ version = "0.1.2"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma" git = "https://github.com/girlbossceo/ruwuma"
#branch = "conduwuit-changes" #branch = "conduwuit-changes"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5" rev = "c76e2873c1593a3308d4ba3e0e4a1db65acf8536"
features = [ features = [
"compat", "compat",
"rand", "rand",
@@ -293,6 +317,7 @@ features = [
"server-util", "server-util",
"unstable-exhaustive-types", "unstable-exhaustive-types",
"ring-compat", "ring-compat",
"identifiers-validation",
"unstable-unspecified", "unstable-unspecified",
"unstable-msc2448", "unstable-msc2448",
"unstable-msc2666", "unstable-msc2666",
@@ -307,10 +332,6 @@ features = [
"unstable-extensible-events", "unstable-extensible-events",
] ]
[workspace.dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruwuma"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
[workspace.dependencies.rust-rocksdb] [workspace.dependencies.rust-rocksdb]
path = "deps/rust-rocksdb" path = "deps/rust-rocksdb"
package = "rust-rocksdb-uwu" package = "rust-rocksdb-uwu"
@@ -372,24 +393,26 @@ version = "0.34.0"
version = "0.34.0" version = "0.34.0"
# jemalloc usage # jemalloc usage
# locked to 0.5.4 due to static binary linking breakage
[workspace.dependencies.tikv-jemalloc-sys] [workspace.dependencies.tikv-jemalloc-sys]
version = "0.5.4" version = "=0.5.4"
default-features = false default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"] features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemallocator] [workspace.dependencies.tikv-jemallocator]
version = "0.5.4" version = "=0.5.4"
default-features = false default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"] features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemalloc-ctl] [workspace.dependencies.tikv-jemalloc-ctl]
version = "0.5.4" version = "=0.5.4"
default-features = false default-features = false
features = ["use_std"] features = ["use_std"]
[workspace.dependencies.console-subscriber] [workspace.dependencies.console-subscriber]
version = "0.3" version = "0.4"
[workspace.dependencies.nix] [workspace.dependencies.nix]
version = "0.29.0" version = "0.29.0"
default-features = false
features = ["resource"] features = ["resource"]
[workspace.dependencies.sd-notify] [workspace.dependencies.sd-notify]
@@ -415,6 +438,17 @@ default-features = false
[workspace.dependencies.checked_ops] [workspace.dependencies.checked_ops]
version = "0.1" version = "0.1"
[workspace.dependencies.syn]
version = "2.0.72"
default-features = false
features = ["full", "extra-traits"]
[workspace.dependencies.quote]
version = "1.0.36"
[workspace.dependencies.proc-macro2]
version = "1.0.86"
# #
# Patches # Patches
@@ -482,6 +516,11 @@ package = "conduit_core"
path = "src/core" path = "src/core"
default-features = false default-features = false
[workspace.dependencies.conduit-macros]
package = "conduit_macros"
path = "src/macros"
default-features = false
############################################################################### ###############################################################################
# #
# Release profiles # Release profiles
@@ -659,7 +698,16 @@ opt-level = 'z'
# '-Clink-arg=-Wl,-z,nodelete', # '-Clink-arg=-Wl,-z,nodelete',
#] #]
# primarily used for CI
[profile.test] [profile.test]
inherits = "dev"
codegen-units = 16
incremental = false
[profile.test.package.'*']
inherits = "dev"
debug = 0
codegen-units = 16
incremental = false incremental = false
############################################################################### ###############################################################################
@@ -721,19 +769,19 @@ variant_size_differences = "allow"
[workspace.lints.clippy] [workspace.lints.clippy]
################### ###################
cargo = "warn" cargo = { level = "warn", priority = -1 }
## some sadness ## some sadness
multiple_crate_versions = { level = "allow", priority = 1 } multiple_crate_versions = { level = "allow", priority = 1 }
################### ###################
complexity = "warn" complexity = { level = "warn", priority = -1 }
################### ###################
correctness = "warn" correctness = { level = "warn", priority = -1 }
################### ###################
nursery = "warn" nursery = { level = "warn", priority = -1 }
## some sadness ## some sadness
missing_const_for_fn = { level = "allow", priority = 1 } # TODO missing_const_for_fn = { level = "allow", priority = 1 } # TODO
@@ -743,7 +791,7 @@ significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
significant_drop_tightening = { level = "allow", priority = 1 } # TODO significant_drop_tightening = { level = "allow", priority = 1 } # TODO
################### ###################
pedantic = "warn" pedantic = { level = "warn", priority = -1 }
## some sadness ## some sadness
doc_markdown = { level = "allow", priority = 1 } doc_markdown = { level = "allow", priority = 1 }
@@ -762,12 +810,12 @@ unnecessary_wraps = { level = "allow", priority = 1 }
unused_async = { level = "allow", priority = 1 } unused_async = { level = "allow", priority = 1 }
################### ###################
perf = "warn" perf = { level = "warn", priority = -1 }
################### ###################
#restriction = "warn" #restriction = "warn"
allow_attributes = "warn" #allow_attributes = "warn" # UNSTABLE
arithmetic_side_effects = "warn" arithmetic_side_effects = "warn"
as_conversions = "warn" as_conversions = "warn"
as_underscore = "warn" as_underscore = "warn"
@@ -812,14 +860,15 @@ unseparated_literal_suffix = "warn"
verbose_file_reads = "warn" verbose_file_reads = "warn"
################### ###################
style = "warn" style = { level = "warn", priority = -1 }
## some sadness ## some sadness
# trivial assertions are quite alright # trivial assertions are quite alright
assertions_on_constants = { level = "allow", priority = 1 } assertions_on_constants = { level = "allow", priority = 1 }
module_inception = { level = "allow", priority = 1 }
################### ###################
suspicious = "warn" suspicious = { level = "warn", priority = -1 }
## some sadness ## some sadness
let_underscore_future = { level = "allow", priority = 1 } let_underscore_future = { level = "allow", priority = 1 }
+9 -6
View File
@@ -52,9 +52,10 @@
# Performance monitoring/tracing sample rate for Sentry.io # Performance monitoring/tracing sample rate for Sentry.io
# #
# Note that too high values may impact performance, and can be disabled by setting it to 0.0 # Note that too high values may impact performance, and can be disabled by setting it to 0.0 (0%)
# This value is read as a percentage to Sentry, represented as a decimal
# #
# Defaults to 0.15 # Defaults to 15% of traces (0.15)
#sentry_traces_sample_rate = 0.15 #sentry_traces_sample_rate = 0.15
# Whether to attach a stacktrace to Sentry reports. # Whether to attach a stacktrace to Sentry reports.
@@ -223,9 +224,11 @@ registration_token = "change this token for something specific to your server"
# No default. # No default.
# forbidden_alias_names = [] # forbidden_alias_names = []
# List of forbidden server names that we will block all client room joins, incoming federated room directory requests, incoming federated invites for, and incoming federated joins. This check is applied on the room ID, room alias, sender server name, and sender user's server name. # List of forbidden server names that we will block incoming AND outgoing federation with, and block client room joins / remote user invites.
# Basically "global" ACLs. For our user (client) checks, admin users are allowed. #
# No default. # This check is applied on the room ID, room alias, sender server name, sender user's server name, inbound federation X-Matrix origin, and outbound federation handler.
#
# Basically "global" ACLs. No default.
# forbidden_remote_server_names = [] # forbidden_remote_server_names = []
# List of forbidden server names that we will block all outgoing federated room directory requests for. Useful for preventing our users from wandering into bad servers or spaces. # List of forbidden server names that we will block all outgoing federated room directory requests for. Useful for preventing our users from wandering into bad servers or spaces.
@@ -401,7 +404,7 @@ allow_profile_lookup_federation_requests = true
# setting this to false may reduce startup time. # setting this to false may reduce startup time.
# #
# Enabled by default. # Enabled by default.
#media_statup_check = true #media_startup_check = true
# OpenID token expiration/TTL in seconds # OpenID token expiration/TTL in seconds
# #
-1
View File
@@ -16,7 +16,6 @@ case "$1" in
--home "$CONDUWUIT_DATABASE_PATH" \ --home "$CONDUWUIT_DATABASE_PATH" \
--disabled-login \ --disabled-login \
--shell "/usr/sbin/nologin" \ --shell "/usr/sbin/nologin" \
--verbose \
conduwuit conduwuit
fi fi
+1
View File
@@ -10,6 +10,7 @@
- [Docker](deploying/docker.md) - [Docker](deploying/docker.md)
- [Arch Linux](deploying/arch-linux.md) - [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md) - [Debian](deploying/debian.md)
- [FreeBSD](deploying/freebsd.md)
- [TURN](turn.md) - [TURN](turn.md)
- [Appservices](appservices.md) - [Appservices](appservices.md)
- [Maintenance](maintenance.md) - [Maintenance](maintenance.md)
+24 -4
View File
@@ -4,15 +4,35 @@ This chapter describes various ways to configure conduwuit.
## Basics ## Basics
Conduwuit uses a config file for the majority of the settings. Please refer to the conduwuit uses a config file for the majority of the settings, but also supports setting individual config options via commandline.
[example config file](./configuration/examples.md#example-configuration) for all of those settings.
The config file to use can either be specified on the command line when running conduwuit by specifying the Please refer to the [example config file](./configuration/examples.md#example-configuration) for all of those settings.
The config file to use can be specified on the commandline when running conduwuit by specifying the
`-c`, `--config` flag. Alternatively, you can use the environment variable `CONDUWUIT_CONFIG` to specify the config `-c`, `--config` flag. Alternatively, you can use the environment variable `CONDUWUIT_CONFIG` to specify the config
file to used. file to used. Conduit's environment variables are supported for backwards compatibility.
## Option commandline flag
conduwuit supports setting individual config options in TOML format from the `-O` / `--option` flag. For example, you can set your server name via `-O server_name=\"example.com\"`.
Note that the config is parsed as TOML, and shells like bash will remove quotes. So unfortunately it is required to escape quotes if the config option takes a string.
This does not apply to options that take booleans or numbers:
- `--option allow_registration=true` works ✅
- `-O max_request_size=99999999` works ✅
- `-O server_name=example.com` does not work ❌
- `--option log=\"debug\"` works ✅
- `--option server_name='"example.com'"` works ✅
## Environment variables ## Environment variables
All of the settings that are found in the config file can be specified by using environment variables. All of the settings that are found in the config file can be specified by using environment variables.
The environment variable names should be all caps and prefixed with `CONDUWUIT_`. The environment variable names should be all caps and prefixed with `CONDUWUIT_`.
For example, if the setting you are changing is `max_request_size`, then the environment variable to set is For example, if the setting you are changing is `max_request_size`, then the environment variable to set is
`CONDUWUIT_MAX_REQUEST_SIZE`. `CONDUWUIT_MAX_REQUEST_SIZE`.
To modify config options not in the `[global]` context such as `[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
Conduit's environment variables are supported for backwards compatibility (e.g. `CONDUIT_SERVER_NAME`).
+8
View File
@@ -0,0 +1,8 @@
# conduwuit for FreeBSD
conduwuit at the moment does not provide FreeBSD builds. Building conduwuit on FreeBSD requires a specific environment variable to use the
system prebuilt RocksDB library instead of rust-rocksdb / rust-librocksdb-sys which does *not* work and will cause a build error or coredump.
Use the following environment variable: `ROCKSDB_LIB_DIR=/usr/local/lib`
Such example commandline with it can be: `ROCKSDB_LIB_DIR=/usr/local/lib cargo build --release`
+2 -4
View File
@@ -9,14 +9,12 @@
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. You may simply download the binary that fits your machine. Run `uname -m` to see what you need.
Prebuilt binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest). Prebuilt fully static musl binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or `main` CI branch workflow artifact output. These also include Debian packages. These binaries have jemalloc and io_uring statically linked and included with them.
The latest tagged release also includes the Debian packages.
Alternatively, you may compile the binary yourself. We recommend using [Lix](https://lix.systems) to build conduwuit as this has the most guaranteed Alternatively, you may compile the binary yourself. We recommend using [Lix](https://lix.systems) to build conduwuit as this has the most guaranteed
reproducibiltiy and easiest to get a build environment and output going. reproducibiltiy and easiest to get a build environment and output going.
Otherwise, follow standard Rust project build guides (installing git and cloning the repo, getting the Rust toolchain via rustup, installing LLVM toolchain + libclang, installing liburing for io_uring and RocksDB, etc). Otherwise, follow standard Rust project build guides (installing git and cloning the repo, getting the Rust toolchain via rustup, installing LLVM toolchain + libclang for RocksDB, installing liburing for io_uring and RocksDB, etc).
## Adding a conduwuit user ## Adding a conduwuit user
+11 -2
View File
@@ -8,6 +8,17 @@
> >
> If there are things like Compose file issues or Dockerhub image issues, those can still be mentioned as long as they're something we can fix. > If there are things like Compose file issues or Dockerhub image issues, those can still be mentioned as long as they're something we can fix.
## General potential issues
#### Potential DNS issues when using Docker
Docker has issues with its default DNS setup that may cause DNS to not be properly functional when running conduwuit, resulting in federation issues.
The symptoms of this have shown in excessively long room joins (30+ minutes) from very long DNS timeouts, log entries of "mismatching responding nameservers", and/or partial or non-functional inbound/outbound federation.
This is **not** a conduwuit issue, and is purely a Docker issue. It is not sustainable for heavy DNS activity which is normal for Matrix federation. The workarounds for this are:
- Use DNS over TCP via the config option `query_over_tcp_only = true`
- Don't use Docker's default DNS setup and instead allow the container to use and communicate with your host's DNS servers (host's `/etc/resolv.conf`)
## Rocksdb / database issues ## Rocksdb / database issues
#### Direct IO #### Direct IO
@@ -38,8 +49,6 @@ With this in mind:
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored - Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database - If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
## Media
## Debugging ## Debugging
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`. Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
+18 -3
View File
@@ -60,6 +60,11 @@ name = "markdownlint"
group = "versions" group = "versions"
script = "markdownlint --version" script = "markdownlint --version"
[[task]]
name = "dpkg"
group = "versions"
script = "dpkg --version"
[[task]] [[task]]
name = "cargo-audit" name = "cargo-audit"
group = "security" group = "security"
@@ -68,7 +73,9 @@ script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
[[task]] [[task]]
name = "cargo-fmt" name = "cargo-fmt"
group = "lints" group = "lints"
script = "cargo fmt --check -- --color=always" script = """
cargo fmt --check -- --color=always
"""
[[task]] [[task]]
name = "cargo-doc" name = "cargo-doc"
@@ -79,6 +86,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \ direnv exec . \
cargo doc \ cargo doc \
--workspace \ --workspace \
--profile test \
--all-features \ --all-features \
--no-deps \ --no-deps \
--document-private-items \ --document-private-items \
@@ -91,6 +99,7 @@ group = "lints"
script = """ script = """
cargo clippy \ cargo clippy \
--workspace \ --workspace \
--profile test \
--all-targets \ --all-targets \
--color=always \ --color=always \
-- \ -- \
@@ -105,6 +114,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \ direnv exec . \
cargo clippy \ cargo clippy \
--workspace \ --workspace \
--profile test \
--all-targets \ --all-targets \
--all-features \ --all-features \
--color=always \ --color=always \
@@ -118,6 +128,7 @@ group = "lints"
script = """ script = """
cargo clippy \ cargo clippy \
--workspace \ --workspace \
--profile test \
--features jemalloc \ --features jemalloc \
--all-targets \ --all-targets \
--color=always \ --color=always \
@@ -156,6 +167,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \ direnv exec . \
cargo test \ cargo test \
--workspace \ --workspace \
--profile test \
--all-targets \ --all-targets \
--all-features \ --all-features \
--color=always \ --color=always \
@@ -169,6 +181,7 @@ group = "tests"
script = """ script = """
cargo test \ cargo test \
--workspace \ --workspace \
--profile test \
--all-targets \ --all-targets \
--color=always \ --color=always \
-- \ -- \
@@ -185,9 +198,11 @@ name = "nix-default"
group = "tests" group = "tests"
script = """ script = """
env DIRENV_DEVSHELL=dynamic \ env DIRENV_DEVSHELL=dynamic \
CARGO_PROFILE="test" \
direnv exec . \ direnv exec . \
bin/nix-build-and-cache just .#default bin/nix-build-and-cache just .#default-test
env DIRENV_DEVSHELL=dynamic \ env DIRENV_DEVSHELL=dynamic \
CARGO_PROFILE="test" \
direnv exec . \ direnv exec . \
nix run -L .#default -- --help nix run -L .#default-test -- --help && nix run -L .#default-test -- --version
""" """
+113 -1
View File
@@ -24,7 +24,7 @@
file = ./rust-toolchain.toml; file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8"; sha256 = "sha256-6eN/GKzjVSjEhGO9FhWObkRFaE1Jf+uqMSdQnb8lcB4=";
}; };
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: { mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -119,6 +119,9 @@
# Needed for producing Debian packages # Needed for producing Debian packages
cargo-deb cargo-deb
# Needed for CI to check validity of produced Debian packages (dpkg-deb)
dpkg
# Needed for Complement # Needed for Complement
go go
@@ -133,6 +136,12 @@
# Useful for editing the book locally # Useful for editing the book locally
mdbook mdbook
# used for rust caching in CI to speed it up
sccache
# needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo
]) ])
++ scope.main.buildInputs ++ scope.main.buildInputs
++ scope.main.propagatedBuildInputs ++ scope.main.propagatedBuildInputs
@@ -144,6 +153,15 @@
{ {
packages = { packages = {
default = scopeHost.main; default = scopeHost.main;
default-debug = scopeHost.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
default-test = scopeHost.main.override {
profile = "test";
disable_release_max_log_level = true;
};
all-features = scopeHost.main.override { all-features = scopeHost.main.override {
all_features = true; all_features = true;
disable_features = [ disable_features = [
@@ -153,6 +171,18 @@
"experimental" "experimental"
]; ];
}; };
all-features-debug = scopeHost.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; }; hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
oci-image = scopeHost.oci-image; oci-image = scopeHost.oci-image;
@@ -167,6 +197,20 @@
]; ];
}; };
}; };
oci-image-all-features-debug = scopeHost.oci-image.override {
main = scopeHost.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
};
oci-image-hmalloc = scopeHost.oci-image.override { oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override { main = scopeHost.main.override {
features = ["hardened_malloc"]; features = ["hardened_malloc"];
@@ -201,6 +245,26 @@
value = scopeCrossStatic.main; value = scopeCrossStatic.main;
} }
# An output for a statically-linked unstripped debug ("dev") binary
{
name = "${binaryName}-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
}
# An output for a statically-linked unstripped debug binary with the
# "test" profile (for CI usage only)
{
name = "${binaryName}-test";
value = scopeCrossStatic.main.override {
profile = "test";
disable_release_max_log_level = true;
};
}
# An output for a statically-linked binary with `--all-features` # An output for a statically-linked binary with `--all-features`
{ {
name = "${binaryName}-all-features"; name = "${binaryName}-all-features";
@@ -215,6 +279,23 @@
}; };
} }
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
{
name = "${binaryName}-all-features-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
}
# An output for a statically-linked binary with hardened_malloc # An output for a statically-linked binary with hardened_malloc
{ {
name = "${binaryName}-hmalloc"; name = "${binaryName}-hmalloc";
@@ -229,6 +310,18 @@
value = scopeCrossStatic.oci-image; value = scopeCrossStatic.oci-image;
} }
# An output for an OCI image based on that unstripped debug ("dev") binary
{
name = "oci-image-${crossSystem}-debug";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
};
}
# An output for an OCI image based on that binary with `--all-features` # An output for an OCI image based on that binary with `--all-features`
{ {
name = "oci-image-${crossSystem}-all-features"; name = "oci-image-${crossSystem}-all-features";
@@ -245,6 +338,25 @@
}; };
} }
# An output for an OCI image based on that unstripped debug ("dev") binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features-debug";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
};
}
# An output for an OCI image based on that binary with hardened_malloc # An output for an OCI image based on that binary with hardened_malloc
{ {
name = "oci-image-${crossSystem}-hmalloc"; name = "oci-image-${crossSystem}-hmalloc";
+1 -1
View File
@@ -14,7 +14,7 @@ yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
ip_range_denylist = [] ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"] url_preview_domain_contains_allowlist = ["*"]
media_compat_file_link = false media_compat_file_link = false
media_statup_check = false media_startup_check = false
rocksdb_direct_io = false rocksdb_direct_io = false
[global.tls] [global.tls]
+9 -2
View File
@@ -14,8 +14,15 @@
let let
main' = main.override { main' = main.override {
profile = "dev"; profile = "test";
features = ["axum_dual_protocol"]; all_features = true;
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
}; };
start = writeShellScriptBin "start" '' start = writeShellScriptBin "start" ''
+20 -4
View File
@@ -7,6 +7,7 @@
, liburing , liburing
, pkgsBuildHost , pkgsBuildHost
, rocksdb , rocksdb
, removeReferencesTo
, rust , rust
, rust-jemalloc-sys , rust-jemalloc-sys
, stdenv , stdenv
@@ -51,6 +52,10 @@ rust-jemalloc-sys' = (rust-jemalloc-sys.override {
unprefixed = true; unprefixed = true;
}).overrideAttrs (old: { }).overrideAttrs (old: {
configureFlags = old.configureFlags ++ configureFlags = old.configureFlags ++
# we dont need docs
[ "--disable-doc" ] ++
# we dont need cxx/C++ integration
[ "--disable-cxx" ] ++
# tikv-jemalloc-sys/profiling feature # tikv-jemalloc-sys/profiling feature
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof"; lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
}); });
@@ -90,8 +95,8 @@ buildDepsOnlyEnv =
else if stdenv.targetPlatform.isAarch64 else if stdenv.targetPlatform.isAarch64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isAarch64 [ ++ lib.optionals stdenv.targetPlatform.isAarch64 [
# cortex-a55 == ARMv8.2-a # cortex-a73 == ARMv8-A
"-DPORTABLE=armv8.2-a" "-DPORTABLE=armv8-a"
] ]
else old.cmakeFlags; else old.cmakeFlags;
}); });
@@ -124,7 +129,7 @@ buildPackageEnv = {
+ lib.optionalString stdenv.targetPlatform.isx86_64 + lib.optionalString stdenv.targetPlatform.isx86_64
" -Ctarget-cpu=x86-64-v2" " -Ctarget-cpu=x86-64-v2"
+ lib.optionalString stdenv.targetPlatform.isAarch64 + lib.optionalString stdenv.targetPlatform.isAarch64
" -Ctarget-cpu=cortex-a55"; # cortex-a55 == ARMv8.2-a " -Ctarget-cpu=cortex-a73"; # cortex-a73 == ARMv8-A
}; };
@@ -149,7 +154,8 @@ commonAttrs = {
]; ];
}; };
dontStrip = profile == "dev"; dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test";
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'; buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
@@ -164,6 +170,9 @@ commonAttrs = {
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious # differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents. # rebuilds of bindgen and its depedents.
jq jq
# needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo
] ]
++ lib.optionals stdenv.isDarwin [ ++ lib.optionals stdenv.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242 # https://github.com/NixOS/nixpkgs/issues/206242
@@ -173,6 +182,13 @@ commonAttrs = {
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612 # https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security pkgsBuildHost.darwin.apple_sdk.frameworks.Security
]; ];
# for some reason gcc and other weird deps are added to OCI images and bloats it up
#
# <https://github.com/input-output-hk/haskell.nix/issues/829>
postInstall = with pkgsBuildHost; ''
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${libgcc} -t ${linuxHeaders} -t ${libidn2} -t ${libunistring} '{}' +
'';
}; };
in in
+1 -1
View File
@@ -11,7 +11,7 @@
# If you're having trouble making the relevant changes, bug a maintainer. # If you're having trouble making the relevant changes, bug a maintainer.
[toolchain] [toolchain]
channel = "1.77.0" channel = "1.80.0"
components = [ components = [
# For rust-analyzer # For rust-analyzer
"rust-src", "rust-src",
+1
View File
@@ -29,6 +29,7 @@ release_max_log_level = [
clap.workspace = true clap.workspace = true
conduit-api.workspace = true conduit-api.workspace = true
conduit-core.workspace = true conduit-core.workspace = true
conduit-macros.workspace = true
conduit-service.workspace = true conduit-service.workspace = true
const-str.workspace = true const-str.workspace = true
futures-util.workspace = true futures-util.workspace = true
+66
View File
@@ -0,0 +1,66 @@
use clap::Parser;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::{
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug,
debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query,
query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand,
};
#[derive(Debug, Parser)]
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(super) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
Appservices(AppserviceCommand),
#[command(subcommand)]
/// - Commands for managing local users
Users(UserCommand),
#[command(subcommand)]
/// - Commands for managing rooms
Rooms(RoomCommand),
#[command(subcommand)]
/// - Commands for managing federation
Federation(FederationCommand),
#[command(subcommand)]
/// - Commands for managing the server
Server(ServerCommand),
#[command(subcommand)]
/// - Commands for managing media
Media(MediaCommand),
#[command(subcommand)]
/// - Commands for checking integrity
Check(CheckCommand),
#[command(subcommand)]
/// - Commands for debugging things
Debug(DebugCommand),
#[command(subcommand)]
/// - Low-level queries for database getters and iterators
Query(QueryCommand),
}
#[tracing::instrument(skip_all, name = "command")]
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
use AdminCommand::*;
Ok(match command {
Appservices(command) => appservice::process(command, context).await?,
Media(command) => media::process(command, context).await?,
Users(command) => user::process(command, context).await?,
Rooms(command) => room::process(command, context).await?,
Federation(command) => federation::process(command, context).await?,
Server(command) => server::process(command, context).await?,
Debug(command) => debug::process(command, context).await?,
Query(command) => query::process(command, context).await?,
Check(command) => check::process(command, context).await?,
})
}
+18 -11
View File
@@ -1,18 +1,20 @@
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent}; use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
use crate::{services, Result}; use crate::{admin_command, Result};
pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let appservice_config = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config); let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config { match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml).await { Ok(yaml) => match self.services.appservice.register_appservice(yaml).await {
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!( Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {id}." "Appservice registered with ID: {id}."
))), ))),
@@ -26,8 +28,10 @@ pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent>
} }
} }
pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> { #[admin_command]
match services() pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice .appservice
.unregister_appservice(&appservice_identifier) .unregister_appservice(&appservice_identifier)
.await .await
@@ -39,8 +43,10 @@ pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String)
} }
} }
pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> { #[admin_command]
match services() pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice .appservice
.get_registration(&appservice_identifier) .get_registration(&appservice_identifier)
.await .await
@@ -54,8 +60,9 @@ pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Res
} }
} }
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let appservices = services().appservice.iter_ids().await; pub(super) async fn list_registered(&self) -> Result<RoomMessageEventContent> {
let appservices = self.services.appservice.iter_ids().await;
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", ")); let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
Ok(RoomMessageEventContent::text_plain(output)) Ok(RoomMessageEventContent::text_plain(output))
} }
+7 -19
View File
@@ -2,12 +2,11 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[derive(Debug, Subcommand)]
#[derive(Subcommand)] #[admin_command_dispatch]
pub(super) enum AppserviceCommand { pub(super) enum AppserviceCommand {
/// - Register an appservice using its registration YAML /// - Register an appservice using its registration YAML
/// ///
@@ -29,24 +28,13 @@ pub(super) enum AppserviceCommand {
/// - Show an appservice's config using its ID /// - Show an appservice's config using its ID
/// ///
/// You can find the ID using the `list-appservices` command. /// You can find the ID using the `list-appservices` command.
Show { #[clap(alias("show"))]
ShowAppserviceConfig {
/// The appservice to show /// The appservice to show
appservice_identifier: String, appservice_identifier: String,
}, },
/// - List all the currently registered appservices /// - List all the currently registered appservices
List, #[clap(alias("list"))]
} ListRegistered,
pub(super) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
AppserviceCommand::Register => register(body).await?,
AppserviceCommand::Unregister {
appservice_identifier,
} => unregister(body, appservice_identifier).await?,
AppserviceCommand::Show {
appservice_identifier,
} => show(body, appservice_identifier).await?,
AppserviceCommand::List => list(body).await?,
})
} }
+5 -3
View File
@@ -1,14 +1,16 @@
use conduit::Result; use conduit::Result;
use conduit_macros::implement;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::services; use crate::Command;
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over /// Uses the iterator in `src/database/key_value/users.rs` to iterator over
/// every user in our database (remote and local). Reports total count, any /// every user in our database (remote and local). Reports total count, any
/// errors if there were any, etc /// errors if there were any, etc
pub(super) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[implement(Command, params = "<'_>")]
pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().users.db.iter(); let results = self.services.users.db.iter();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
let users = results.collect::<Vec<_>>(); let users = results.collect::<Vec<_>>();
+4 -5
View File
@@ -4,16 +4,15 @@ use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::Command;
#[cfg_attr(test, derive(Debug))] #[derive(Debug, Subcommand)]
#[derive(Subcommand)]
pub(super) enum CheckCommand { pub(super) enum CheckCommand {
AllUsers, AllUsers,
} }
pub(super) async fn process(command: CheckCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn process(command: CheckCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
Ok(match command { Ok(match command {
CheckCommand::AllUsers => check_all_users(body).await?, CheckCommand::AllUsers => context.check_all_users().await?,
}) })
} }
+6
View File
@@ -0,0 +1,6 @@
use service::Services;
pub(crate) struct Command<'a> {
pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str],
}
+248 -103
View File
@@ -1,33 +1,37 @@
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
fmt::Write,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Instant, time::{Instant, SystemTime},
}; };
use api::client::validate_and_add_event_id; use api::client::validate_and_add_event_id;
use conduit::{ use conduit::{
debug, info, log, debug, info, log,
log::{capture, Capture}, log::{capture, Capture},
warn, Error, Result, utils, warn, Error, PduEvent, Result,
}; };
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation::event::get_room_state}, api::{client::error::ErrorKind, federation::event::get_room_state},
events::room::message::RoomMessageEventContent, events::room::message::RoomMessageEventContent,
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName, CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
}; };
use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve_actual_dest, services, PduEvent};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
pub(super) async fn echo(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> { use crate::admin_command;
#[admin_command]
pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" "); let message = message.join(" ");
Ok(RoomMessageEventContent::notice_plain(message)) Ok(RoomMessageEventContent::notice_plain(message))
} }
pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let event_id = Arc::<EventId>::from(event_id); let event_id = Arc::<EventId>::from(event_id);
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? { if let Some(event) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
let room_id_str = event let room_id_str = event
.get("room_id") .get("room_id")
.and_then(|val| val.as_str()) .and_then(|val| val.as_str())
@@ -35,13 +39,16 @@ pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
let room_id = <&RoomId>::try_from(room_id_str) let room_id = <&RoomId>::try_from(room_id_str)
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
let start = Instant::now(); let start = Instant::now();
let count = services() let count = self
.services
.rooms .rooms
.auth_chain .auth_chain
.event_ids_iter(room_id, vec![event_id]) .event_ids_iter(room_id, vec![event_id])
.await? .await?
.count(); .count();
let elapsed = start.elapsed(); let elapsed = start.elapsed();
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
"Loaded auth chain with length {count} in {elapsed:?}" "Loaded auth chain with length {count} in {elapsed:?}"
@@ -51,14 +58,16 @@ pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
} }
} }
pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().saturating_sub(1)].join("\n"); let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) { Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
Ok(hash) => { Ok(hash) => {
@@ -79,15 +88,17 @@ pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent
} }
} }
pub(super) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let mut outlier = false; let mut outlier = false;
let mut pdu_json = services() let mut pdu_json = self
.services
.rooms .rooms
.timeline .timeline
.get_non_outlier_pdu_json(&event_id)?; .get_non_outlier_pdu_json(&event_id)?;
if pdu_json.is_none() { if pdu_json.is_none() {
outlier = true; outlier = true;
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?; pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id)?;
} }
match pdu_json { match pdu_json {
Some(json) => { Some(json) => {
@@ -106,69 +117,84 @@ pub(super) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<
} }
} }
#[admin_command]
pub(super) async fn get_remote_pdu_list( pub(super) async fn get_remote_pdu_list(
body: Vec<&str>, server: Box<ServerName>, force: bool, &self, server: Box<ServerName>, force: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server == services().globals.server_name() { if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \ "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
the database.", the database.",
)); ));
} }
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let list = body let list = self
.clone() .body
.drain(1..body.len().checked_sub(1).unwrap()) .iter()
.collect::<Vec<_>>()
.drain(1..self.body.len().saturating_sub(1))
.filter_map(|pdu| EventId::parse(pdu).ok()) .filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut failed_count: usize = 0;
let mut success_count: usize = 0;
for pdu in list { for pdu in list {
if force { if force {
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await { if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await {
services() failed_count = failed_count.saturating_add(1);
self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"Failed to get remote PDU, ignoring error: {e}" "Failed to get remote PDU, ignoring error: {e}"
))) )))
.await; .await;
warn!(%e, "Failed to get remote PDU, ignoring error"); warn!("Failed to get remote PDU, ignoring error: {e}");
} else {
success_count = success_count.saturating_add(1);
} }
} else { } else {
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?; self.get_remote_pdu(Box::from(pdu), server.clone()).await?;
success_count = success_count.saturating_add(1);
} }
} }
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs.")) Ok(RoomMessageEventContent::text_plain(format!(
"Fetched {success_count} remote PDUs successfully with {failed_count} failures"
)))
} }
#[admin_command]
pub(super) async fn get_remote_pdu( pub(super) async fn get_remote_pdu(
_body: Vec<&str>, event_id: Box<EventId>, server: Box<ServerName>, &self, event_id: Box<EventId>, server: Box<ServerName>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server == services().globals.server_name() { if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
)); ));
} }
match services() match self
.services
.sending .sending
.send_federation_request( .send_federation_request(
&server, &server,
@@ -189,7 +215,11 @@ pub(super) async fn get_remote_pdu(
debug!("Attempting to parse PDU: {:?}", &response.pdu); debug!("Attempting to parse PDU: {:?}", &response.pdu);
let parsed_pdu = { let parsed_pdu = {
let parsed_result = parse_incoming_pdu(&response.pdu); let parsed_result = self
.services
.rooms
.event_handler
.parse_incoming_pdu(&response.pdu);
let (event_id, value, room_id) = match parsed_result { let (event_id, value, room_id) = match parsed_result {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
@@ -207,7 +237,7 @@ pub(super) async fn get_remote_pdu(
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
debug!("Attempting to fetch homeserver signing keys for {server}"); debug!("Attempting to fetch homeserver signing keys for {server}");
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
@@ -217,7 +247,7 @@ pub(super) async fn get_remote_pdu(
}); });
info!("Attempting to handle event ID {event_id} as backfilled PDU"); info!("Attempting to handle event ID {event_id} as backfilled PDU");
services() self.services
.rooms .rooms
.timeline .timeline
.backfill_pdu(&server, response.pdu, &pub_key_map) .backfill_pdu(&server, response.pdu, &pub_key_map)
@@ -236,9 +266,11 @@ pub(super) async fn get_remote_pdu(
} }
} }
pub(super) async fn get_room_state(_body: Vec<&str>, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> { #[admin_command]
let room_id = services().rooms.alias.resolve(&room).await?; pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_state = services() let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state = self
.services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full(&room_id) .room_state_full(&room_id)
@@ -263,8 +295,9 @@ pub(super) async fn get_room_state(_body: Vec<&str>, room: OwnedRoomOrAliasId) -
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```"))) Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
} }
pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<RoomMessageEventContent> { #[admin_command]
if server == services().globals.server_name() { pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves.", "Not allowed to send federation requests to ourselves.",
)); ));
@@ -272,7 +305,8 @@ pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
match services() match self
.services
.sending .sending
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {}) .send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.await .await
@@ -301,23 +335,23 @@ pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
} }
} }
pub(super) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventContent> {
// Force E2EE device list updates for all users // Force E2EE device list updates for all users
for user_id in services().users.iter().filter_map(Result::ok) { for user_id in self.services.users.iter().filter_map(Result::ok) {
services().users.mark_device_key_update(&user_id)?; self.services.users.mark_device_key_update(&user_id)?;
} }
Ok(RoomMessageEventContent::text_plain( Ok(RoomMessageEventContent::text_plain(
"Marked all devices for all users as having new keys to update", "Marked all devices for all users as having new keys to update",
)) ))
} }
pub(super) async fn change_log_level( #[admin_command]
_body: Vec<&str>, filter: Option<String>, reset: bool, pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
let handles = &["console"]; let handles = &["console"];
if reset { if reset {
let old_filter_layer = match EnvFilter::try_new(&services().globals.config.log) { let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!( return Ok(RoomMessageEventContent::text_plain(format!(
@@ -326,7 +360,8 @@ pub(super) async fn change_log_level(
}, },
}; };
match services() match self
.services
.server .server
.log .log
.reload .reload
@@ -335,7 +370,7 @@ pub(super) async fn change_log_level(
Ok(()) => { Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!( return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}", "Successfully changed log level back to config value {}",
services().globals.config.log self.services.globals.config.log
))); )));
}, },
Err(e) => { Err(e) => {
@@ -356,7 +391,8 @@ pub(super) async fn change_log_level(
}, },
}; };
match services() match self
.services
.server .server
.log .log
.reload .reload
@@ -376,19 +412,21 @@ pub(super) async fn change_log_level(
Ok(RoomMessageEventContent::text_plain("No log level was specified.")) Ok(RoomMessageEventContent::text_plain("No log level was specified."))
} }
pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(mut value) => { Ok(mut value) => {
ruma::signatures::sign_json( ruma::signatures::sign_json(
services().globals.server_name().as_str(), self.services.globals.server_name().as_str(),
services().globals.keypair(), self.services.globals.keypair(),
&mut value, &mut value,
) )
.expect("our request json is what ruma expects"); .expect("our request json is what ruma expects");
@@ -399,19 +437,21 @@ pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent
} }
} }
pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(value) => { Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
@@ -429,19 +469,22 @@ pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventConte
} }
} }
#[tracing::instrument(skip(_body))] #[admin_command]
pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[tracing::instrument(skip(self))]
if !services() pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let first_pdu = services() let first_pdu = self
.services
.rooms .rooms
.timeline .timeline
.first_pdu_in_room(&room_id)? .first_pdu_in_room(&room_id)?
@@ -450,19 +493,22 @@ pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) ->
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}"))) Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
} }
#[tracing::instrument(skip(_body))] #[admin_command]
pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[tracing::instrument(skip(self))]
if !services() pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let latest_pdu = services() let latest_pdu = self
.services
.rooms .rooms
.timeline .timeline
.latest_pdu_in_room(&room_id)? .latest_pdu_in_room(&room_id)?
@@ -471,32 +517,36 @@ pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}"))) Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
} }
#[tracing::instrument(skip(_body))] #[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn force_set_room_state_from_server( pub(super) async fn force_set_room_state_from_server(
_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>, &self, room_id: Box<RoomId>, server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services() if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let first_pdu = services() let first_pdu = self
.services
.rooms .rooms
.timeline .timeline
.latest_pdu_in_room(&room_id)? .latest_pdu_in_room(&room_id)?
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?; .ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
let room_version = services().rooms.state.get_room_version(&room_id)?; let room_version = self.services.rooms.state.get_room_version(&room_id)?;
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new(); let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
let remote_state_response = services() let remote_state_response = self
.services
.sending .sending
.send_federation_request( .send_federation_request(
&server_name, &server_name,
@@ -510,7 +560,7 @@ pub(super) async fn force_set_room_state_from_server(
let mut events = Vec::with_capacity(remote_state_response.pdus.len()); let mut events = Vec::with_capacity(remote_state_response.pdus.len());
for pdu in remote_state_response.pdus.clone() { for pdu in remote_state_response.pdus.clone() {
events.push(match parse_incoming_pdu(&pdu) { events.push(match self.services.rooms.event_handler.parse_incoming_pdu(&pdu) {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
warn!("Could not parse PDU, ignoring: {e}"); warn!("Could not parse PDU, ignoring: {e}");
@@ -520,7 +570,7 @@ pub(super) async fn force_set_room_state_from_server(
} }
info!("Fetching required signing keys for all the state events we got"); info!("Fetching required signing keys for all the state events we got");
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
@@ -530,7 +580,7 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response for result in remote_state_response
.pdus .pdus
.iter() .iter()
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map)) .map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
{ {
let Ok((event_id, value)) = result.await else { let Ok((event_id, value)) = result.await else {
continue; continue;
@@ -541,12 +591,13 @@ pub(super) async fn force_set_room_state_from_server(
Error::BadServerResponse("Invalid PDU in send_join response.") Error::BadServerResponse("Invalid PDU in send_join response.")
})?; })?;
services() self.services
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value)?; .add_pdu_outlier(&event_id, &value)?;
if let Some(state_key) = &pdu.state_key { if let Some(state_key) = &pdu.state_key {
let shortstatekey = services() let shortstatekey = self
.services
.rooms .rooms
.short .short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?; .get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
@@ -558,32 +609,34 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response for result in remote_state_response
.auth_chain .auth_chain
.iter() .iter()
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map)) .map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
{ {
let Ok((event_id, value)) = result.await else { let Ok((event_id, value)) = result.await else {
continue; continue;
}; };
services() self.services
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value)?; .add_pdu_outlier(&event_id, &value)?;
} }
let new_room_state = services() let new_room_state = self
.services
.rooms .rooms
.event_handler .event_handler
.resolve_state(room_id.clone().as_ref(), &room_version, state) .resolve_state(room_id.clone().as_ref(), &room_version, state)
.await?; .await?;
info!("Forcing new room state"); info!("Forcing new room state");
let (short_state_hash, new, removed) = services() let (short_state_hash, new, removed) = self
.services
.rooms .rooms
.state_compressor .state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)?; .save_state(room_id.clone().as_ref(), new_room_state)?;
let state_lock = services().rooms.state.mutex.lock(&room_id).await; let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
services() self.services
.rooms .rooms
.state .state
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock) .force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
@@ -593,7 +646,10 @@ pub(super) async fn force_set_room_state_from_server(
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \ "Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
m.room.member state" m.room.member state"
); );
services().rooms.state_cache.update_joined_count(&room_id)?; self.services
.rooms
.state_cache
.update_joined_count(&room_id)?;
drop(state_lock); drop(state_lock);
@@ -602,16 +658,64 @@ pub(super) async fn force_set_room_state_from_server(
)) ))
} }
pub(super) async fn resolve_true_destination( #[admin_command]
_body: Vec<&str>, server_name: Box<ServerName>, no_cache: bool, pub(super) async fn get_signing_keys(
&self, server_name: Option<Box<ServerName>>, _cached: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let signing_keys = self.services.globals.signing_keys_for(&server_name)?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"```rs\n{signing_keys:#?}\n```"
)))
}
#[admin_command]
#[allow(dead_code)]
pub(super) async fn get_verify_keys(
&self, server_name: Option<Box<ServerName>>, cached: bool,
) -> Result<RoomMessageEventContent> {
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let mut out = String::new();
if cached {
writeln!(out, "| Key ID | VerifyKey |")?;
writeln!(out, "| --- | --- |")?;
for (key_id, verify_key) in self.services.globals.verify_keys_for(&server_name)? {
writeln!(out, "| {key_id} | {verify_key:?} |")?;
}
return Ok(RoomMessageEventContent::notice_markdown(out));
}
let signature_ids: Vec<String> = Vec::new();
let keys = self
.services
.rooms
.event_handler
.fetch_signing_keys_for_server(&server_name, signature_ids)
.await?;
writeln!(out, "| Key ID | Public Key |")?;
writeln!(out, "| --- | --- |")?;
for (key_id, key) in keys {
writeln!(out, "| {key_id} | {key} |")?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
}
#[admin_command]
pub(super) async fn resolve_true_destination(
&self, server_name: Box<ServerName>, no_cache: bool,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server_name == services().globals.config.server_name { if server_name == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
)); ));
@@ -623,12 +727,16 @@ pub(super) async fn resolve_true_destination(
&& matches!(data.span_name(), "actual" | "well-known" | "srv") && matches!(data.span_name(), "actual" | "well-known" | "srv")
}; };
let state = &services().server.log.capture; let state = &self.services.server.log.capture;
let logs = Arc::new(Mutex::new(String::new())); let logs = Arc::new(Mutex::new(String::new()));
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone())); let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
let capture_scope = capture.start(); let capture_scope = capture.start();
let actual = resolve_actual_dest(&server_name, !no_cache).await?; let actual = self
.services
.resolver
.resolve_actual_dest(&server_name, !no_cache)
.await?;
drop(capture_scope); drop(capture_scope);
let msg = format!( let msg = format!(
@@ -640,23 +748,26 @@ pub(super) async fn resolve_true_destination(
Ok(RoomMessageEventContent::text_markdown(msg)) Ok(RoomMessageEventContent::text_markdown(msg))
} }
#[must_use] #[admin_command]
pub(super) fn memory_stats() -> RoomMessageEventContent { pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
let html_body = conduit::alloc::memory_stats(); let html_body = conduit::alloc::memory_stats();
if html_body.is_none() { if html_body.is_none() {
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc."); return Ok(RoomMessageEventContent::text_plain(
"malloc stats are not supported on your compiled malloc.",
));
} }
RoomMessageEventContent::text_html( Ok(RoomMessageEventContent::text_html(
"This command's output can only be viewed by clients that render HTML.".to_owned(), "This command's output can only be viewed by clients that render HTML.".to_owned(),
html_body.expect("string result"), html_body.expect("string result"),
) ))
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let out = services().server.metrics.runtime_metrics().map_or_else( pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
let out = self.services.server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"), |metrics| format!("```rs\n{metrics:#?}\n```"),
); );
@@ -665,15 +776,17 @@ pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEvent
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown( Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.", "Runtime metrics require building with `tokio_unstable`.",
)) ))
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let out = services().server.metrics.runtime_interval().map_or_else( pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
let out = self.services.server.metrics.runtime_interval().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"), |metrics| format!("```rs\n{metrics:#?}\n```"),
); );
@@ -682,8 +795,40 @@ pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEven
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown( Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.", "Runtime metrics require building with `tokio_unstable`.",
)) ))
} }
#[admin_command]
pub(super) async fn time(&self) -> Result<RoomMessageEventContent> {
let now = SystemTime::now();
Ok(RoomMessageEventContent::text_markdown(utils::time::format(now, "%+")))
}
#[admin_command]
pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageEventContent> {
if names {
let out = info::cargo::dependencies_names().join(" ");
return Ok(RoomMessageEventContent::notice_markdown(out));
}
let deps = info::cargo::dependencies();
let mut out = String::new();
writeln!(out, "| name | version | features |")?;
writeln!(out, "| ---- | ------- | -------- |")?;
for (name, dep) in deps {
let version = dep.try_req().unwrap_or("*");
let feats = dep.req_features();
let feats = if !feats.is_empty() {
feats.join(" ")
} else {
String::new()
};
writeln!(out, "{name} | {version} | {feats}")?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
}
+23 -59
View File
@@ -3,13 +3,13 @@ pub(crate) mod tester;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName}; use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use tester::TesterCommand;
use self::commands::*; use self::tester::TesterCommand;
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum DebugCommand { pub(super) enum DebugCommand {
/// - Echo input of admin command /// - Echo input of admin command
Echo { Echo {
@@ -76,6 +76,14 @@ pub(super) enum DebugCommand {
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
}, },
/// - Get and display signing keys from local cache or remote server.
GetSigningKeys {
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
cached: bool,
},
/// - Sends a federation request to the remote server's /// - Sends a federation request to the remote server's
/// `/_matrix/federation/v1/version` endpoint and measures the latency it /// `/_matrix/federation/v1/version` endpoint and measures the latency it
/// took for the server to respond /// took for the server to respond
@@ -167,61 +175,17 @@ pub(super) enum DebugCommand {
/// invocation. /// invocation.
RuntimeInterval, RuntimeInterval,
/// - Print the current time
Time,
/// - List dependencies
ListDependencies {
#[arg(short, long)]
names: bool,
},
/// - Developer test stubs /// - Developer test stubs
#[command(subcommand)] #[command(subcommand)]
#[allow(non_snake_case)]
Tester(TesterCommand), Tester(TesterCommand),
} }
pub(super) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
DebugCommand::Echo {
message,
} => echo(body, message).await?,
DebugCommand::GetAuthChain {
event_id,
} => get_auth_chain(body, event_id).await?,
DebugCommand::ParsePdu => parse_pdu(body).await?,
DebugCommand::GetPdu {
event_id,
} => get_pdu(body, event_id).await?,
DebugCommand::GetRemotePdu {
event_id,
server,
} => get_remote_pdu(body, event_id, server).await?,
DebugCommand::GetRoomState {
room_id,
} => get_room_state(body, room_id).await?,
DebugCommand::Ping {
server,
} => ping(body, server).await?,
DebugCommand::ForceDeviceListUpdates => force_device_list_updates(body).await?,
DebugCommand::ChangeLogLevel {
filter,
reset,
} => change_log_level(body, filter, reset).await?,
DebugCommand::SignJson => sign_json(body).await?,
DebugCommand::VerifyJson => verify_json(body).await?,
DebugCommand::FirstPduInRoom {
room_id,
} => first_pdu_in_room(body, room_id).await?,
DebugCommand::LatestPduInRoom {
room_id,
} => latest_pdu_in_room(body, room_id).await?,
DebugCommand::GetRemotePduList {
server,
force,
} => get_remote_pdu_list(body, server, force).await?,
DebugCommand::ForceSetRoomStateFromServer {
room_id,
server_name,
} => force_set_room_state_from_server(body, server_name, room_id).await?,
DebugCommand::ResolveTrueDestination {
server_name,
no_cache,
} => resolve_true_destination(body, server_name, no_cache).await?,
DebugCommand::MemoryStats => memory_stats(),
DebugCommand::RuntimeMetrics => runtime_metrics(body).await?,
DebugCommand::RuntimeInterval => runtime_interval(body).await?,
DebugCommand::Tester(command) => tester::process(command, body).await?,
})
}
+8 -13
View File
@@ -1,34 +1,29 @@
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::Result; use crate::{admin_command, admin_command_dispatch, Result};
#[derive(clap::Subcommand)] #[admin_command_dispatch]
#[cfg_attr(test, derive(Debug))] #[derive(Debug, clap::Subcommand)]
pub(crate) enum TesterCommand { pub(crate) enum TesterCommand {
Tester, Tester,
Timer, Timer,
} }
pub(super) async fn process(command: TesterCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
TesterCommand::Tester => tester(body).await,
TesterCommand::Timer => timer(body).await,
}
}
#[inline(never)] #[inline(never)]
#[rustfmt::skip] #[rustfmt::skip]
#[allow(unused_variables)] #[allow(unused_variables)]
async fn tester(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn tester(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::notice_plain("completed")) Ok(RoomMessageEventContent::notice_plain("completed"))
} }
#[inline(never)] #[inline(never)]
#[rustfmt::skip] #[rustfmt::skip]
async fn timer(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn timer(&self) -> Result<RoomMessageEventContent> {
let started = std::time::Instant::now(); let started = std::time::Instant::now();
timed(&body); timed(self.body);
let elapsed = started.elapsed(); let elapsed = started.elapsed();
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}"))) Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))
+23 -17
View File
@@ -1,21 +1,26 @@
use std::fmt::Write; use std::fmt::Write;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId}; use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
use crate::{escape_html, get_room_info, services, Result}; use crate::{admin_command, escape_html, get_room_info};
pub(super) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
services().rooms.metadata.disable_room(&room_id, true)?; pub(super) async fn disable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
self.services.rooms.metadata.disable_room(&room_id, true)?;
Ok(RoomMessageEventContent::text_plain("Room disabled.")) Ok(RoomMessageEventContent::text_plain("Room disabled."))
} }
pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
services().rooms.metadata.disable_room(&room_id, false)?; pub(super) async fn enable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
self.services.rooms.metadata.disable_room(&room_id, false)?;
Ok(RoomMessageEventContent::text_plain("Room enabled.")) Ok(RoomMessageEventContent::text_plain("Room enabled."))
} }
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let map = services() pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> {
let map = self
.services
.rooms .rooms
.event_handler .event_handler
.federation_handletime .federation_handletime
@@ -31,11 +36,10 @@ pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageE
Ok(RoomMessageEventContent::text_plain(&msg)) Ok(RoomMessageEventContent::text_plain(&msg))
} }
pub(super) async fn fetch_support_well_known( #[admin_command]
_body: Vec<&str>, server_name: Box<ServerName>, pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let response = self
let response = services() .services
.globals
.client .client
.default .default
.get(format!("https://{server_name}/.well-known/matrix/support")) .get(format!("https://{server_name}/.well-known/matrix/support"))
@@ -73,25 +77,27 @@ pub(super) async fn fetch_support_well_known(
))) )))
} }
pub(super) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> { #[admin_command]
if user_id.server_name() == services().globals.config.server_name { pub(super) async fn remote_user_in_rooms(&self, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
if user_id.server_name() == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"User belongs to our server, please use `list-joined-rooms` user admin command instead.", "User belongs to our server, please use `list-joined-rooms` user admin command instead.",
)); ));
} }
if !services().users.exists(&user_id)? { if !self.services.users.exists(&user_id)? {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Remote user does not exist in our database.", "Remote user does not exist in our database.",
)); ));
} }
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services() let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|room_id| get_room_info(&room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect(); .collect();
if rooms.is_empty() { if rooms.is_empty() {
+4 -22
View File
@@ -2,12 +2,12 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId}; use ruma::{RoomId, ServerName, UserId};
use self::commands::*; use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum FederationCommand { pub(super) enum FederationCommand {
/// - List all rooms we are currently handling an incoming pdu from /// - List all rooms we are currently handling an incoming pdu from
IncomingFederation, IncomingFederation,
@@ -40,21 +40,3 @@ pub(super) enum FederationCommand {
user_id: Box<UserId>, user_id: Box<UserId>,
}, },
} }
pub(super) async fn process(command: FederationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
FederationCommand::DisableRoom {
room_id,
} => disable_room(body, room_id).await?,
FederationCommand::EnableRoom {
room_id,
} => enable_room(body, room_id).await?,
FederationCommand::IncomingFederation => incoming_federation(body).await?,
FederationCommand::FetchSupportWellKnown {
server_name,
} => fetch_support_well_known(body, server_name).await?,
FederationCommand::RemoteUserInRooms {
user_id,
} => remote_user_in_rooms(body, user_id).await?,
})
}
+64 -93
View File
@@ -1,7 +1,7 @@
use std::{panic::AssertUnwindSafe, time::Instant}; use std::{panic::AssertUnwindSafe, sync::Arc, time::Instant};
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use conduit::{error, trace, Error}; use conduit::{checked, error, trace, utils::string::common_prefix, Error, Result};
use futures_util::future::FutureExt; use futures_util::future::FutureExt;
use ruma::{ use ruma::{
events::{ events::{
@@ -10,82 +10,37 @@ use ruma::{
}, },
OwnedEventId, OwnedEventId,
}; };
use service::{
extern crate conduit_service as service; admin::{CommandInput, CommandOutput, CommandResult, HandlerResult},
Services,
use conduit::{utils::string::common_prefix, Result};
pub(crate) use service::admin::{Command, Service};
use service::admin::{CommandOutput, CommandResult, HandlerResult};
use crate::{
appservice, appservice::AppserviceCommand, check, check::CheckCommand, debug, debug::DebugCommand, federation,
federation::FederationCommand, media, media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand,
server, server::ServerCommand, services, user, user::UserCommand,
}; };
pub(crate) const PAGE_SIZE: usize = 100;
#[derive(Parser)] use crate::{admin, admin::AdminCommand, Command};
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
Appservices(AppserviceCommand),
#[command(subcommand)] #[must_use]
/// - Commands for managing local users pub(super) fn complete(line: &str) -> String { complete_command(AdminCommand::command(), line) }
Users(UserCommand),
#[command(subcommand)] #[must_use]
/// - Commands for managing rooms pub(super) fn handle(services: Arc<Services>, command: CommandInput) -> HandlerResult {
Rooms(RoomCommand), Box::pin(handle_command(services, command))
#[command(subcommand)]
/// - Commands for managing federation
Federation(FederationCommand),
#[command(subcommand)]
/// - Commands for managing the server
Server(ServerCommand),
#[command(subcommand)]
/// - Commands for managing media
Media(MediaCommand),
#[command(subcommand)]
/// - Commands for checking integrity
Check(CheckCommand),
#[command(subcommand)]
/// - Commands for debugging things
Debug(DebugCommand),
#[command(subcommand)]
/// - Low-level queries for database getters and iterators
Query(QueryCommand),
} }
#[must_use]
pub(crate) fn handle(command: Command) -> HandlerResult { Box::pin(handle_command(command)) }
#[must_use]
pub(crate) fn complete(line: &str) -> String { complete_admin_command(AdminCommand::command(), line) }
#[tracing::instrument(skip_all, name = "admin")] #[tracing::instrument(skip_all, name = "admin")]
async fn handle_command(command: Command) -> CommandResult { async fn handle_command(services: Arc<Services>, command: CommandInput) -> CommandResult {
AssertUnwindSafe(process_command(&command)) AssertUnwindSafe(Box::pin(process_command(services, &command)))
.catch_unwind() .catch_unwind()
.await .await
.map_err(Error::from_panic) .map_err(Error::from_panic)
.or_else(|error| handle_panic(&error, command)) .or_else(|error| handle_panic(&error, command))
} }
async fn process_command(command: &Command) -> CommandOutput { async fn process_command(services: Arc<Services>, command: &CommandInput) -> CommandOutput {
process_admin_message(&command.command) process(services, &command.command)
.await .await
.and_then(|content| reply(content, command.reply_id.clone())) .and_then(|content| reply(content, command.reply_id.clone()))
} }
fn handle_panic(error: &Error, command: Command) -> CommandResult { fn handle_panic(error: &Error, command: CommandInput) -> CommandResult {
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺"; let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}"); let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
let content = RoomMessageEventContent::notice_markdown(msg); let content = RoomMessageEventContent::notice_markdown(msg);
@@ -104,21 +59,28 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -
} }
// Parse and process a message from the admin room // Parse and process a message from the admin room
async fn process_admin_message(msg: &str) -> CommandOutput { async fn process(services: Arc<Services>, msg: &str) -> CommandOutput {
let mut lines = msg.lines().filter(|l| !l.trim().is_empty()); let lines = msg.lines().filter(|l| !l.trim().is_empty());
let command = lines.next().expect("each string has at least one line"); let command = lines
let body = lines.collect::<Vec<_>>(); .clone()
let parsed = match parse_admin_command(command) { .next()
.expect("each string has at least one line");
let (parsed, body) = match parse_command(command) {
Ok(parsed) => parsed, Ok(parsed) => parsed,
Err(error) => { Err(error) => {
let server_name = services().globals.server_name(); let server_name = services.globals.server_name();
let message = error.replace("server.name", server_name.as_str()); let message = error.replace("server.name", server_name.as_str());
return Some(RoomMessageEventContent::notice_markdown(message)); return Some(RoomMessageEventContent::notice_markdown(message));
}, },
}; };
let body = parse_body(AdminCommand::command(), &body, lines.skip(1).collect()).expect("trailing body parsed");
let context = Command {
services: &services,
body: &body,
};
let timer = Instant::now(); let timer = Instant::now();
let result = process_admin_command(parsed, body).await; let result = Box::pin(admin::process(parsed, &context)).await;
let elapsed = timer.elapsed(); let elapsed = timer.elapsed();
conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}"); conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}");
match result { match result {
@@ -129,31 +91,41 @@ async fn process_admin_message(msg: &str) -> CommandOutput {
} }
} }
#[tracing::instrument(skip_all, name = "command")]
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
let reply_message_content = match command {
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
AdminCommand::Media(command) => media::process(command, body).await?,
AdminCommand::Users(command) => user::process(command, body).await?,
AdminCommand::Rooms(command) => room::process(command, body).await?,
AdminCommand::Federation(command) => federation::process(command, body).await?,
AdminCommand::Server(command) => server::process(command, body).await?,
AdminCommand::Debug(command) => debug::process(command, body).await?,
AdminCommand::Query(command) => query::process(command, body).await?,
AdminCommand::Check(command) => check::process(command, body).await?,
};
Ok(reply_message_content)
}
// Parse chat messages from the admin room into an AdminCommand object // Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> { fn parse_command(command_line: &str) -> Result<(AdminCommand, Vec<String>), String> {
let argv = parse_command_line(command_line); let argv = parse_line(command_line);
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string()) let com = AdminCommand::try_parse_from(&argv).map_err(|error| error.to_string())?;
Ok((com, argv))
} }
fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String { fn parse_body<'a>(mut cmd: clap::Command, body: &'a [String], lines: Vec<&'a str>) -> Result<Vec<&'a str>> {
let argv = parse_command_line(line); let mut start = 1;
'token: for token in body.iter().skip(1) {
let cmd_ = cmd.clone();
for sub in cmd_.get_subcommands() {
if sub.get_name() == *token {
start = checked!(start + 1)?;
cmd = sub.clone();
continue 'token;
}
}
// positional arguments have to be skipped too
let num_posargs = cmd_.get_positionals().count();
start = checked!(start + num_posargs)?;
break;
}
Ok(body
.iter()
.skip(start)
.map(String::as_str)
.chain(lines)
.collect::<Vec<&'a str>>())
}
fn complete_command(mut cmd: clap::Command, line: &str) -> String {
let argv = parse_line(line);
let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1)); let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));
'token: for token in argv.into_iter().skip(1) { 'token: for token in argv.into_iter().skip(1) {
@@ -196,7 +168,7 @@ fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String {
} }
// Parse chat messages from the admin room into an AdminCommand object // Parse chat messages from the admin room into an AdminCommand object
fn parse_command_line(command_line: &str) -> Vec<String> { fn parse_line(command_line: &str) -> Vec<String> {
let mut argv = command_line let mut argv = command_line
.split_whitespace() .split_whitespace()
.map(str::to_owned) .map(str::to_owned)
@@ -208,8 +180,7 @@ fn parse_command_line(command_line: &str) -> Vec<String> {
} }
// First indice has to be "admin" but for console convenience we add it here // First indice has to be "admin" but for console convenience we add it here
let server_user = services().globals.server_user.as_str(); if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with('@') {
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with(server_user) {
argv.insert(0, "admin".to_owned()); argv.insert(0, "admin".to_owned());
} }
+20 -17
View File
@@ -1,11 +1,11 @@
use conduit::Result; use conduit::{debug, info, Result};
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri}; use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use tracing::{debug, info};
use crate::services; use crate::admin_command;
#[admin_command]
pub(super) async fn delete( pub(super) async fn delete(
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>, &self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if event_id.is_some() && mxc.is_some() { if event_id.is_some() && mxc.is_some() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
@@ -15,7 +15,7 @@ pub(super) async fn delete(
if let Some(mxc) = mxc { if let Some(mxc) = mxc {
debug!("Got MXC URL: {mxc}"); debug!("Got MXC URL: {mxc}");
services().media.delete(mxc.as_ref()).await?; self.services.media.delete(mxc.as_ref()).await?;
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Deleted the MXC from our database and on our filesystem.", "Deleted the MXC from our database and on our filesystem.",
@@ -27,7 +27,7 @@ pub(super) async fn delete(
let mut mxc_deletion_count: usize = 0; let mut mxc_deletion_count: usize = 0;
// parsing the PDU for any MXC URLs begins here // parsing the PDU for any MXC URLs begins here
if let Some(event_json) = services().rooms.timeline.get_pdu_json(&event_id)? { if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
if let Some(content_key) = event_json.get("content") { if let Some(content_key) = event_json.get("content") {
debug!("Event ID has \"content\"."); debug!("Event ID has \"content\".");
let content_obj = content_key.as_object(); let content_obj = content_key.as_object();
@@ -123,7 +123,7 @@ pub(super) async fn delete(
} }
for mxc_url in mxc_urls { for mxc_url in mxc_urls {
services().media.delete(&mxc_url).await?; self.services.media.delete(&mxc_url).await?;
mxc_deletion_count = mxc_deletion_count.saturating_add(1); mxc_deletion_count = mxc_deletion_count.saturating_add(1);
} }
@@ -138,23 +138,26 @@ pub(super) async fn delete(
)) ))
} }
pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let mxc_list = body let mxc_list = self
.clone() .body
.drain(1..body.len().checked_sub(1).unwrap()) .to_vec()
.drain(1..self.body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut mxc_deletion_count: usize = 0; let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list { for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk"); debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc).await?; self.services.media.delete(mxc).await?;
mxc_deletion_count = mxc_deletion_count mxc_deletion_count = mxc_deletion_count
.checked_add(1) .checked_add(1)
.expect("mxc_deletion_count should not get this high"); .expect("mxc_deletion_count should not get this high");
@@ -165,10 +168,10 @@ pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventConte
))) )))
} }
pub(super) async fn delete_past_remote_media( #[admin_command]
_body: Vec<&str>, duration: String, force: bool, pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let deleted_count = self
let deleted_count = services() .services
.media .media
.delete_all_remote_media_at_after_time(duration, force) .delete_all_remote_media_at_after_time(duration, force)
.await?; .await?;
+4 -18
View File
@@ -2,12 +2,12 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri}; use ruma::{EventId, MxcUri};
use self::commands::*; use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum MediaCommand { pub(super) enum MediaCommand {
/// - Deletes a single media file from our database and on the filesystem /// - Deletes a single media file from our database and on the filesystem
/// via a single MXC URL /// via a single MXC URL
@@ -37,17 +37,3 @@ pub(super) enum MediaCommand {
force: bool, force: bool,
}, },
} }
pub(super) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
MediaCommand::Delete {
mxc,
event_id,
} => delete(body, mxc, event_id).await?,
MediaCommand::DeleteList => delete_list(body).await?,
MediaCommand::DeletePastRemoteMedia {
duration,
force,
} => delete_past_remote_media(body, duration, force).await?,
})
}
+22 -21
View File
@@ -1,54 +1,55 @@
#![recursion_limit = "192"]
#![allow(clippy::wildcard_imports)] #![allow(clippy::wildcard_imports)]
#![allow(clippy::enum_glob_use)]
pub(crate) mod admin;
pub(crate) mod command;
pub(crate) mod handler;
mod tests;
pub(crate) mod utils;
pub(crate) mod appservice; pub(crate) mod appservice;
pub(crate) mod check; pub(crate) mod check;
pub(crate) mod debug; pub(crate) mod debug;
pub(crate) mod federation; pub(crate) mod federation;
pub(crate) mod handler;
pub(crate) mod media; pub(crate) mod media;
pub(crate) mod query; pub(crate) mod query;
pub(crate) mod room; pub(crate) mod room;
pub(crate) mod server; pub(crate) mod server;
mod tests;
pub(crate) mod user; pub(crate) mod user;
pub(crate) mod utils;
extern crate conduit_api as api; extern crate conduit_api as api;
extern crate conduit_core as conduit; extern crate conduit_core as conduit;
extern crate conduit_service as service; extern crate conduit_service as service;
pub(crate) use conduit::{mod_ctor, mod_dtor, Result}; pub(crate) use conduit::Result;
pub(crate) use service::{services, user_is_local}; pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
pub(crate) use crate::{ pub(crate) use crate::{
handler::Service, command::Command,
utils::{escape_html, get_room_info}, utils::{escape_html, get_room_info},
}; };
mod_ctor! {} pub(crate) const PAGE_SIZE: usize = 100;
mod_dtor! {}
conduit::mod_ctor! {}
conduit::mod_dtor! {}
conduit::rustc_flags_capture! {}
/// Install the admin command handler /// Install the admin command handler
pub async fn init() { pub async fn init(admin_service: &service::admin::Service) {
_ = services() _ = admin_service
.admin
.complete .complete
.write() .write()
.expect("locked for writing") .expect("locked for writing")
.insert(handler::complete); .insert(handler::complete);
_ = services() _ = admin_service.handle.write().await.insert(handler::handle);
.admin
.handle
.write()
.await
.insert(handler::handle);
} }
/// Uninstall the admin command handler /// Uninstall the admin command handler
pub async fn fini() { pub async fn fini(admin_service: &service::admin::Service) {
_ = services().admin.handle.write().await.take(); _ = admin_service.handle.write().await.take();
_ = services() _ = admin_service
.admin
.complete .complete
.write() .write()
.expect("locked for writing") .expect("locked for writing")
+38 -8
View File
@@ -1,18 +1,48 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
RoomId, UserId,
};
use super::AccountData; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(crate) enum AccountDataCommand {
/// - Returns all changes to the account data that happened after `since`.
ChangesSince {
/// Full user ID
user_id: Box<UserId>,
/// UNIX timestamp since (u64)
since: u64,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
/// - Searches the account data for a specific kind.
Get {
/// Full user ID
user_id: Box<UserId>,
/// Account data event type
kind: RoomAccountDataEventType,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
}
/// All the getters and iterators from src/database/key_value/account_data.rs /// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
AccountData::ChangesSince { AccountDataCommand::ChangesSince {
user_id, user_id,
since, since,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.account_data .account_data
.changes_since(room_id.as_deref(), &user_id, since)?; .changes_since(room_id.as_deref(), &user_id, since)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@@ -21,13 +51,13 @@ pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
AccountData::Get { AccountDataCommand::Get {
user_id, user_id,
kind, kind,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.account_data .account_data
.get(room_id.as_deref(), &user_id, kind)?; .get(room_id.as_deref(), &user_id, kind)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
+23 -7
View File
@@ -1,16 +1,32 @@
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use super::Appservice; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(crate) enum AppserviceCommand {
/// - Gets the appservice registration info/details from the ID as a string
GetRegistration {
/// Appservice registration ID
appservice_id: Box<str>,
},
/// - Gets all appservice registrations with their ID and registration info
All,
}
/// All the getters and iterators from src/database/key_value/appservice.rs /// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Appservice::GetRegistration { AppserviceCommand::GetRegistration {
appservice_id, appservice_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.appservice .appservice
.db .db
.get_registration(appservice_id.as_ref()); .get_registration(appservice_id.as_ref());
@@ -20,9 +36,9 @@ pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Appservice::All => { AppserviceCommand::All => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().appservice.all(); let results = services.appservice.all();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
+35 -14
View File
@@ -1,52 +1,73 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
use super::Globals; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs
pub(crate) enum GlobalsCommand {
DatabaseVersion,
CurrentCount,
LastCheckForUpdatesId,
LoadKeypair,
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
/// for the server.
SigningKeysFor {
origin: Box<ServerName>,
},
}
/// All the getters and iterators from src/database/key_value/globals.rs /// All the getters and iterators from src/database/key_value/globals.rs
pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Globals::DatabaseVersion => { GlobalsCommand::DatabaseVersion => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.database_version(); let results = services.globals.db.database_version();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::CurrentCount => { GlobalsCommand::CurrentCount => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.current_count(); let results = services.globals.db.current_count();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::LastCheckForUpdatesId => { GlobalsCommand::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().updates.last_check_for_updates_id(); let results = services.updates.last_check_for_updates_id();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::LoadKeypair => { GlobalsCommand::LoadKeypair => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.load_keypair(); let results = services.globals.db.load_keypair();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::SigningKeysFor { GlobalsCommand::SigningKeysFor {
origin, origin,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.signing_keys_for(&origin); let results = services.globals.db.verify_keys_for(&origin);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
+15 -278
View File
@@ -10,314 +10,51 @@ mod users;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use room_state_cache::room_state_cache;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId,
};
use self::{ use self::{
account_data::account_data, appservice::appservice, globals::globals, presence::presence, resolver::resolver, account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
room_alias::room_alias, sending::sending, users::users, presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
}; };
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
/// Query tables from database /// Query tables from database
pub(super) enum QueryCommand { pub(super) enum QueryCommand {
/// - account_data.rs iterators and getters /// - account_data.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
AccountData(AccountData), AccountData(AccountDataCommand),
/// - appservice.rs iterators and getters /// - appservice.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Appservice(Appservice), Appservice(AppserviceCommand),
/// - presence.rs iterators and getters /// - presence.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Presence(Presence), Presence(PresenceCommand),
/// - rooms/alias.rs iterators and getters /// - rooms/alias.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
RoomAlias(RoomAlias), RoomAlias(RoomAliasCommand),
/// - rooms/state_cache iterators and getters /// - rooms/state_cache iterators and getters
#[command(subcommand)] #[command(subcommand)]
RoomStateCache(RoomStateCache), RoomStateCache(RoomStateCacheCommand),
/// - globals.rs iterators and getters /// - globals.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Globals(Globals), Globals(GlobalsCommand),
/// - sending.rs iterators and getters /// - sending.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Sending(Sending), Sending(SendingCommand),
/// - users.rs iterators and getters /// - users.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Users(Users), Users(UsersCommand),
/// - resolver service /// - resolver service
#[command(subcommand)] #[command(subcommand)]
Resolver(Resolver), Resolver(ResolverCommand),
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) enum AccountData {
/// - Returns all changes to the account data that happened after `since`.
ChangesSince {
/// Full user ID
user_id: Box<UserId>,
/// UNIX timestamp since (u64)
since: u64,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
/// - Searches the account data for a specific kind.
Get {
/// Full user ID
user_id: Box<UserId>,
/// Account data event type
kind: RoomAccountDataEventType,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) enum Appservice {
/// - Gets the appservice registration info/details from the ID as a string
GetRegistration {
/// Appservice registration ID
appservice_id: Box<str>,
},
/// - Gets all appservice registrations with their ID and registration info
All,
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs
pub(super) enum Presence {
/// - Returns the latest presence event for the given user.
GetPresence {
/// Full user ID
user_id: Box<UserId>,
},
/// - Iterator of the most recent presence updates that happened after the
/// event with id `since`.
PresenceSince {
/// UNIX timestamp since (u64)
since: u64,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(super) enum RoomAlias {
ResolveLocalAlias {
/// Full room alias
alias: Box<RoomAliasId>,
},
/// - Iterator of all our local room aliases for the room ID
LocalAliasesForRoom {
/// Full room ID
room_id: Box<RoomId>,
},
/// - Iterator of all our local aliases in our database with their room IDs
AllLocalAliases,
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum RoomStateCache {
ServerInRoom {
server: Box<ServerName>,
room_id: Box<RoomId>,
},
RoomServers {
room_id: Box<RoomId>,
},
ServerRooms {
server: Box<ServerName>,
},
RoomMembers {
room_id: Box<RoomId>,
},
LocalUsersInRoom {
room_id: Box<RoomId>,
},
ActiveLocalUsersInRoom {
room_id: Box<RoomId>,
},
RoomJoinedCount {
room_id: Box<RoomId>,
},
RoomInvitedCount {
room_id: Box<RoomId>,
},
RoomUserOnceJoined {
room_id: Box<RoomId>,
},
RoomMembersInvited {
room_id: Box<RoomId>,
},
GetInviteCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
GetLeftCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
RoomsJoined {
user_id: Box<UserId>,
},
RoomsLeft {
user_id: Box<UserId>,
},
RoomsInvited {
user_id: Box<UserId>,
},
InviteState {
user_id: Box<UserId>,
room_id: Box<RoomId>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs
pub(super) enum Globals {
DatabaseVersion,
CurrentCount,
LastCheckForUpdatesId,
LoadKeypair,
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
/// for the server.
SigningKeysFor {
origin: Box<ServerName>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs
pub(super) enum Sending {
/// - Queries database for all `servercurrentevent_data`
ActiveRequests,
/// - Queries database for `servercurrentevent_data` but for a specific
/// destination
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
ActiveRequestsFor {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
/// - Queries database for `servernameevent_data` which are the queued up
/// requests that will eventually be sent
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
QueuedRequests {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
GetLatestEduCount {
server_name: Box<ServerName>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs
pub(super) enum Users {
Iter,
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// Resolver service and caches
pub(super) enum Resolver {
/// Query the destinations cache
DestinationsCache {
server_name: Option<OwnedServerName>,
},
/// Query the overrides cache
OverridesCache {
name: Option<String>,
},
}
/// Processes admin query commands
pub(super) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
QueryCommand::AccountData(command) => account_data(command).await?,
QueryCommand::Appservice(command) => appservice(command).await?,
QueryCommand::Presence(command) => presence(command).await?,
QueryCommand::RoomAlias(command) => room_alias(command).await?,
QueryCommand::RoomStateCache(command) => room_state_cache(command).await?,
QueryCommand::Globals(command) => globals(command).await?,
QueryCommand::Sending(command) => sending(command).await?,
QueryCommand::Users(command) => users(command).await?,
QueryCommand::Resolver(command) => resolver(command).await?,
})
} }
+28 -8
View File
@@ -1,27 +1,47 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use super::Presence; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs
pub(crate) enum PresenceCommand {
/// - Returns the latest presence event for the given user.
GetPresence {
/// Full user ID
user_id: Box<UserId>,
},
/// - Iterator of the most recent presence updates that happened after the
/// event with id `since`.
PresenceSince {
/// UNIX timestamp since (u64)
since: u64,
},
}
/// All the getters and iterators in key_value/presence.rs /// All the getters and iterators in key_value/presence.rs
pub(super) async fn presence(subcommand: Presence) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Presence::GetPresence { PresenceCommand::GetPresence {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().presence.db.get_presence(&user_id)?; let results = services.presence.db.get_presence(&user_id)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Presence::PresenceSince { PresenceCommand::PresenceSince {
since, since,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().presence.db.presence_since(since); let results = services.presence.db.presence_since(since);
let presence_since: Vec<(_, _, _)> = results.collect(); let presence_since: Vec<(_, _, _)> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
+27 -20
View File
@@ -1,25 +1,29 @@
use std::fmt::Write; use std::fmt::Write;
use clap::Subcommand;
use conduit::{utils::time, Result}; use conduit::{utils::time, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName}; use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use super::Resolver; use crate::{admin_command, admin_command_dispatch};
use crate::services;
/// All the getters and iterators in key_value/users.rs #[admin_command_dispatch]
pub(super) async fn resolver(subcommand: Resolver) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
match subcommand { /// Resolver service and caches
Resolver::DestinationsCache { pub(crate) enum ResolverCommand {
server_name, /// Query the destinations cache
} => destinations_cache(server_name).await, DestinationsCache {
Resolver::OverridesCache { server_name: Option<OwnedServerName>,
name, },
} => overrides_cache(name).await,
} /// Query the overrides cache
OverridesCache {
name: Option<String>,
},
} }
async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> { #[admin_command]
use service::sending::CachedDest; async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedDest;
let mut out = String::new(); let mut out = String::new();
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?; writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
@@ -36,9 +40,10 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line"); writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
}; };
let map = services() let map = self
.globals .services
.resolver .resolver
.cache
.destinations .destinations
.read() .read()
.expect("locked"); .expect("locked");
@@ -52,8 +57,9 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room
Ok(RoomMessageEventContent::notice_markdown(out)) Ok(RoomMessageEventContent::notice_markdown(out))
} }
async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEventContent> { #[admin_command]
use service::sending::CachedOverride; async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedOverride;
let mut out = String::new(); let mut out = String::new();
writeln!(out, "| Server Name | IP | Port | Expires |")?; writeln!(out, "| Server Name | IP | Port | Expires |")?;
@@ -70,9 +76,10 @@ async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEvent
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line"); writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
}; };
let map = services() let map = self
.globals .services
.resolver .resolver
.cache
.overrides .overrides
.read() .read()
.expect("locked"); .expect("locked");
+31 -10
View File
@@ -1,27 +1,48 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
use super::RoomAlias; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(crate) enum RoomAliasCommand {
ResolveLocalAlias {
/// Full room alias
alias: Box<RoomAliasId>,
},
/// - Iterator of all our local room aliases for the room ID
LocalAliasesForRoom {
/// Full room ID
room_id: Box<RoomId>,
},
/// - Iterator of all our local aliases in our database with their room IDs
AllLocalAliases,
}
/// All the getters and iterators in src/database/key_value/rooms/alias.rs /// All the getters and iterators in src/database/key_value/rooms/alias.rs
pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
RoomAlias::ResolveLocalAlias { RoomAliasCommand::ResolveLocalAlias {
alias, alias,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.resolve_local_alias(&alias); let results = services.rooms.alias.resolve_local_alias(&alias);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomAlias::LocalAliasesForRoom { RoomAliasCommand::LocalAliasesForRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.local_aliases_for_room(&room_id); let results = services.rooms.alias.local_aliases_for_room(&room_id);
let aliases: Vec<_> = results.collect(); let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@@ -29,9 +50,9 @@ pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEvent
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
))) )))
}, },
RoomAlias::AllLocalAliases => { RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.all_local_aliases(); let results = services.rooms.alias.all_local_aliases();
let aliases: Vec<_> = results.collect(); let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
+112 -58
View File
@@ -1,71 +1,136 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
use super::RoomStateCache; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
pub(crate) enum RoomStateCacheCommand {
ServerInRoom {
server: Box<ServerName>,
room_id: Box<RoomId>,
},
RoomServers {
room_id: Box<RoomId>,
},
ServerRooms {
server: Box<ServerName>,
},
RoomMembers {
room_id: Box<RoomId>,
},
LocalUsersInRoom {
room_id: Box<RoomId>,
},
ActiveLocalUsersInRoom {
room_id: Box<RoomId>,
},
RoomJoinedCount {
room_id: Box<RoomId>,
},
RoomInvitedCount {
room_id: Box<RoomId>,
},
RoomUserOnceJoined {
room_id: Box<RoomId>,
},
RoomMembersInvited {
room_id: Box<RoomId>,
},
GetInviteCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
GetLeftCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
RoomsJoined {
user_id: Box<UserId>,
},
RoomsLeft {
user_id: Box<UserId>,
},
RoomsInvited {
user_id: Box<UserId>,
},
InviteState {
user_id: Box<UserId>,
room_id: Box<RoomId>,
},
}
pub(super) async fn process(
subcommand: RoomStateCacheCommand, context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
match subcommand { match subcommand {
RoomStateCache::ServerInRoom { RoomStateCacheCommand::ServerInRoom {
server, server,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let result = services() let result = services.rooms.state_cache.server_in_room(&server, &room_id);
.rooms
.state_cache
.server_in_room(&server, &room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
))) )))
}, },
RoomStateCache::RoomServers { RoomStateCacheCommand::RoomServers {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect();
.rooms
.state_cache
.room_servers(&room_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::ServerRooms { RoomStateCacheCommand::ServerRooms {
server, server,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect(); let results: Result<Vec<_>> = services.rooms.state_cache.server_rooms(&server).collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomMembers { RoomStateCacheCommand::RoomMembers {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect();
.rooms
.state_cache
.room_members(&room_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::LocalUsersInRoom { RoomStateCacheCommand::LocalUsersInRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Vec<_> = services() let results: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.local_users_in_room(&room_id) .local_users_in_room(&room_id)
@@ -76,11 +141,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::ActiveLocalUsersInRoom { RoomStateCacheCommand::ActiveLocalUsersInRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Vec<_> = services() let results: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.active_local_users_in_room(&room_id) .active_local_users_in_room(&room_id)
@@ -91,33 +156,33 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomJoinedCount { RoomStateCacheCommand::RoomJoinedCount {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.state_cache.room_joined_count(&room_id); let results = services.rooms.state_cache.room_joined_count(&room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomInvitedCount { RoomStateCacheCommand::RoomInvitedCount {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.state_cache.room_invited_count(&room_id); let results = services.rooms.state_cache.room_invited_count(&room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomUserOnceJoined { RoomStateCacheCommand::RoomUserOnceJoined {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services
.rooms .rooms
.state_cache .state_cache
.room_useroncejoined(&room_id) .room_useroncejoined(&room_id)
@@ -128,11 +193,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomMembersInvited { RoomStateCacheCommand::RoomMembersInvited {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services
.rooms .rooms
.state_cache .state_cache
.room_members_invited(&room_id) .room_members_invited(&room_id)
@@ -143,12 +208,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::GetInviteCount { RoomStateCacheCommand::GetInviteCount {
room_id, room_id,
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.rooms .rooms
.state_cache .state_cache
.get_invite_count(&room_id, &user_id); .get_invite_count(&room_id, &user_id);
@@ -158,12 +223,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::GetLeftCount { RoomStateCacheCommand::GetLeftCount {
room_id, room_id,
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.rooms .rooms
.state_cache .state_cache
.get_left_count(&room_id, &user_id); .get_left_count(&room_id, &user_id);
@@ -173,56 +238,45 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsJoined { RoomStateCacheCommand::RoomsJoined {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect();
.rooms
.state_cache
.rooms_joined(&user_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsInvited { RoomStateCacheCommand::RoomsInvited {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect();
.rooms
.state_cache
.rooms_invited(&user_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsLeft { RoomStateCacheCommand::RoomsLeft {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect(); let results: Result<Vec<_>> = services.rooms.state_cache.rooms_left(&user_id).collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::InviteState { RoomStateCacheCommand::InviteState {
user_id, user_id,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services.rooms.state_cache.invite_state(&user_id, &room_id);
.rooms
.state_cache
.invite_state(&user_id, &room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
+75 -16
View File
@@ -1,14 +1,73 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
use service::sending::Destination;
use super::Sending; use crate::Command;
use crate::{service::sending::Destination, services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs
pub(crate) enum SendingCommand {
/// - Queries database for all `servercurrentevent_data`
ActiveRequests,
/// - Queries database for `servercurrentevent_data` but for a specific
/// destination
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
ActiveRequestsFor {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
/// - Queries database for `servernameevent_data` which are the queued up
/// requests that will eventually be sent
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
QueuedRequests {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
GetLatestEduCount {
server_name: Box<ServerName>,
},
}
/// All the getters and iterators in key_value/sending.rs /// All the getters and iterators in key_value/sending.rs
pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Sending::ActiveRequests => { SendingCommand::ActiveRequests => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().sending.db.active_requests(); let results = services.sending.db.active_requests();
let active_requests: Result<Vec<(_, _, _)>> = results.collect(); let active_requests: Result<Vec<(_, _, _)>> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@@ -16,7 +75,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
))) )))
}, },
Sending::QueuedRequests { SendingCommand::QueuedRequests {
appservice_id, appservice_id,
server_name, server_name,
user_id, user_id,
@@ -38,12 +97,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.queued_requests(&Destination::Appservice(appservice_id)) .queued_requests(&Destination::Appservice(appservice_id))
}, },
(None, Some(server_name), None, None) => services() (None, Some(server_name), None, None) => services
.sending .sending
.db .db
.queued_requests(&Destination::Normal(server_name.into())), .queued_requests(&Destination::Normal(server_name.into())),
@@ -55,7 +114,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.queued_requests(&Destination::Push(user_id.into(), push_key)) .queued_requests(&Destination::Push(user_id.into(), push_key))
@@ -81,7 +140,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
))) )))
}, },
Sending::ActiveRequestsFor { SendingCommand::ActiveRequestsFor {
appservice_id, appservice_id,
server_name, server_name,
user_id, user_id,
@@ -104,12 +163,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.active_requests_for(&Destination::Appservice(appservice_id)) .active_requests_for(&Destination::Appservice(appservice_id))
}, },
(None, Some(server_name), None, None) => services() (None, Some(server_name), None, None) => services
.sending .sending
.db .db
.active_requests_for(&Destination::Normal(server_name.into())), .active_requests_for(&Destination::Normal(server_name.into())),
@@ -121,7 +180,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.active_requests_for(&Destination::Push(user_id.into(), push_key)) .active_requests_for(&Destination::Push(user_id.into(), push_key))
@@ -147,11 +206,11 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
))) )))
}, },
Sending::GetLatestEduCount { SendingCommand::GetLatestEduCount {
server_name, server_name,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().sending.db.get_latest_educount(&server_name); let results = services.sending.db.get_latest_educount(&server_name);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
+14 -5
View File
@@ -1,14 +1,23 @@
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use super::Users; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs
pub(crate) enum UsersCommand {
Iter,
}
/// All the getters and iterators in key_value/users.rs /// All the getters and iterators in key_value/users.rs
pub(super) async fn users(subcommand: Users) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: UsersCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Users::Iter => { UsersCommand::Iter => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().users.db.iter(); let results = services.users.db.iter();
let users = results.collect::<Vec<_>>(); let users = results.collect::<Vec<_>>();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@@ -1,12 +1,49 @@
use std::fmt::Write; use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId}; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
use super::RoomAliasCommand; use crate::{escape_html, Command};
use crate::{escape_html, services, Result};
pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
let server_user = &services().globals.server_user; pub(crate) enum RoomAliasCommand {
/// - Make an alias point to a room.
Set {
#[arg(short, long)]
/// Set the alias even if a room is already using it
force: bool,
/// The room id to set the alias on
room_id: Box<RoomId>,
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Remove a local alias
Remove {
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Show which room is using an alias
Which {
/// The alias localpart to look up (`alias`, not
/// `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - List aliases currently being used
List {
/// If set, only list the aliases for this room
room_id: Option<Box<RoomId>>,
},
}
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
let server_user = &services.globals.server_user;
match command { match command {
RoomAliasCommand::Set { RoomAliasCommand::Set {
@@ -19,7 +56,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
| RoomAliasCommand::Which { | RoomAliasCommand::Which {
ref room_alias_localpart, ref room_alias_localpart,
} => { } => {
let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name()); let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
let room_alias = match RoomAliasId::parse_box(room_alias_str) { let room_alias = match RoomAliasId::parse_box(room_alias_str) {
Ok(alias) => alias, Ok(alias) => alias,
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))), Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
@@ -29,8 +66,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
force, force,
room_id, room_id,
.. ..
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) { } => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) {
(true, Ok(Some(id))) => match services() (true, Ok(Some(id))) => match services
.rooms .rooms
.alias .alias
.set_alias(&room_alias, &room_id, server_user) .set_alias(&room_alias, &room_id, server_user)
@@ -43,7 +80,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!( (false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite" "Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
))), ))),
(_, Ok(None)) => match services() (_, Ok(None)) => match services
.rooms .rooms
.alias .alias
.set_alias(&room_alias, &room_id, server_user) .set_alias(&room_alias, &room_id, server_user)
@@ -55,8 +92,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
}, },
RoomAliasCommand::Remove { RoomAliasCommand::Remove {
.. ..
} => match services().rooms.alias.resolve_local_alias(&room_alias) { } => match services.rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => match services() Ok(Some(id)) => match services
.rooms .rooms
.alias .alias
.remove_alias(&room_alias, server_user) .remove_alias(&room_alias, server_user)
@@ -70,7 +107,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
}, },
RoomAliasCommand::Which { RoomAliasCommand::Which {
.. ..
} => match services().rooms.alias.resolve_local_alias(&room_alias) { } => match services.rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))), Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")), Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
@@ -84,7 +121,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
room_id, room_id,
} => { } => {
if let Some(room_id) = room_id { if let Some(room_id) = room_id {
let aliases = services() let aliases = services
.rooms .rooms
.alias .alias
.local_aliases_for_room(&room_id) .local_aliases_for_room(&room_id)
@@ -109,14 +146,14 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
} }
} else { } else {
let aliases = services() let aliases = services
.rooms .rooms
.alias .alias
.all_local_aliases() .all_local_aliases()
.collect::<Result<Vec<_>, _>>(); .collect::<Result<Vec<_>, _>>();
match aliases { match aliases {
Ok(aliases) => { Ok(aliases) => {
let server_name = services().globals.server_name(); let server_name = services.globals.server_name();
let plain_list = aliases let plain_list = aliases
.iter() .iter()
.fold(String::new(), |mut output, (alias, id)| { .fold(String::new(), |mut output, (alias, id)| {
@@ -1,15 +1,18 @@
use std::fmt::Write; use std::fmt::Write;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result}; use crate::{admin_command, escape_html, get_room_info, PAGE_SIZE};
pub(super) async fn list( #[admin_command]
_body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, pub(super) async fn list_rooms(
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
// TODO: i know there's a way to do this with clap, but i can't seem to find it // TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1); let page = page.unwrap_or(1);
let mut rooms = services() let mut rooms = self
.services
.rooms .rooms
.metadata .metadata
.iter_ids() .iter_ids()
@@ -18,7 +21,8 @@ pub(super) async fn list(
.ok() .ok()
.filter(|room_id| { .filter(|room_id| {
if exclude_disabled if exclude_disabled
&& services() && self
.services
.rooms .rooms
.metadata .metadata
.is_disabled(room_id) .is_disabled(room_id)
@@ -28,7 +32,8 @@ pub(super) async fn list(
} }
if exclude_banned if exclude_banned
&& services() && self
.services
.rooms .rooms
.metadata .metadata
.is_banned(room_id) .is_banned(room_id)
@@ -39,7 +44,7 @@ pub(super) async fn list(
true true
}) })
.map(|room_id| get_room_info(&room_id)) .map(|room_id| get_room_info(self.services, &room_id))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);
@@ -1,21 +1,43 @@
use std::fmt::Write; use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId}; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId};
use super::RoomDirectoryCommand; use crate::{escape_html, get_room_info, Command, PAGE_SIZE};
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
pub(crate) enum RoomDirectoryCommand {
/// - Publish a room to the room directory
Publish {
/// The room id of the room to publish
room_id: Box<RoomId>,
},
/// - Unpublish a room to the room directory
Unpublish {
/// The room id of the room to unpublish
room_id: Box<RoomId>,
},
/// - List rooms that are published
List {
page: Option<usize>,
},
}
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match command { match command {
RoomDirectoryCommand::Publish { RoomDirectoryCommand::Publish {
room_id, room_id,
} => match services().rooms.directory.set_public(&room_id) { } => match services.rooms.directory.set_public(&room_id) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")), Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
}, },
RoomDirectoryCommand::Unpublish { RoomDirectoryCommand::Unpublish {
room_id, room_id,
} => match services().rooms.directory.set_not_public(&room_id) { } => match services.rooms.directory.set_not_public(&room_id) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")), Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
}, },
@@ -24,12 +46,12 @@ pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
} => { } => {
// TODO: i know there's a way to do this with clap, but i can't seem to find it // TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1); let page = page.unwrap_or(1);
let mut rooms = services() let mut rooms = services
.rooms .rooms
.directory .directory
.public_rooms() .public_rooms()
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|id: OwnedRoomId| get_room_info(&id)) .map(|id: OwnedRoomId| get_room_info(services, &id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);
rooms.reverse(); rooms.reverse();
@@ -1,22 +1,30 @@
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId}; use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use service::services;
use super::RoomInfoCommand; use crate::{admin_command, admin_command_dispatch};
use crate::Result;
pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command_dispatch]
match command { #[derive(Debug, Subcommand)]
RoomInfoCommand::ListJoinedMembers { pub(crate) enum RoomInfoCommand {
room_id, /// - List joined members in a room
} => list_joined_members(body, room_id).await, ListJoinedMembers {
RoomInfoCommand::ViewRoomTopic { room_id: Box<RoomId>,
room_id, },
} => view_room_topic(body, room_id).await,
} /// - Displays room topic
///
/// Room topics can be huge, so this is in its
/// own separate command
ViewRoomTopic {
room_id: Box<RoomId>,
},
} }
async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
let room_name = services() async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let room_name = self
.services
.rooms .rooms
.state_accessor .state_accessor
.get_name(&room_id) .get_name(&room_id)
@@ -24,7 +32,8 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.flatten() .flatten()
.unwrap_or_else(|| room_id.to_string()); .unwrap_or_else(|| room_id.to_string());
let members = services() let members = self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
@@ -35,7 +44,7 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.map(|user_id| { .map(|user_id| {
( (
user_id.clone(), user_id.clone(),
services() self.services
.users .users
.displayname(&user_id) .displayname(&user_id)
.unwrap_or(None) .unwrap_or(None)
@@ -58,12 +67,18 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
Ok(RoomMessageEventContent::notice_markdown(output_plain)) Ok(RoomMessageEventContent::notice_markdown(output_plain))
} }
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else { async fn view_room_topic(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let Some(room_topic) = self
.services
.rooms
.state_accessor
.get_room_topic(&room_id)?
else {
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set.")); return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
}; };
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Room topic:\n\n```{room_topic}\n```" "Room topic:\n```\n{room_topic}\n```"
))) )))
} }
+13 -160
View File
@@ -1,20 +1,23 @@
mod room_alias_commands; mod alias;
mod room_commands; mod commands;
mod room_directory_commands; mod directory;
mod room_info_commands; mod info;
mod room_moderation_commands; mod moderation;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, RoomOrAliasId};
use self::room_commands::list; use self::{
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
};
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum RoomCommand { pub(super) enum RoomCommand {
/// - List all rooms the server knows about /// - List all rooms the server knows about
List { #[clap(alias = "list")]
ListRooms {
page: Option<usize>, page: Option<usize>,
/// Excludes rooms that we have federation disabled with /// Excludes rooms that we have federation disabled with
@@ -42,153 +45,3 @@ pub(super) enum RoomCommand {
/// - Manage the room directory /// - Manage the room directory
Directory(RoomDirectoryCommand), Directory(RoomDirectoryCommand),
} }
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum RoomInfoCommand {
/// - List joined members in a room
ListJoinedMembers {
room_id: Box<RoomId>,
},
/// - Displays room topic
///
/// Room topics can be huge, so this is in its
/// own separate command
ViewRoomTopic {
room_id: Box<RoomId>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum RoomAliasCommand {
/// - Make an alias point to a room.
Set {
#[arg(short, long)]
/// Set the alias even if a room is already using it
force: bool,
/// The room id to set the alias on
room_id: Box<RoomId>,
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Remove a local alias
Remove {
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Show which room is using an alias
Which {
/// The alias localpart to look up (`alias`, not
/// `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - List aliases currently being used
List {
/// If set, only list the aliases for this room
room_id: Option<Box<RoomId>>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum RoomDirectoryCommand {
/// - Publish a room to the room directory
Publish {
/// The room id of the room to publish
room_id: Box<RoomId>,
},
/// - Unpublish a room to the room directory
Unpublish {
/// The room id of the room to unpublish
room_id: Box<RoomId>,
},
/// - List rooms that are published
List {
page: Option<usize>,
},
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum RoomModerationCommand {
/// - Bans a room from local users joining and evicts all our local users
/// from the room. Also blocks any invites (local and remote) for the
/// banned room.
///
/// Server admins (users in the conduwuit admin room) will not be evicted
/// and server admins can still join the room. To evict admins too, use
/// --force (also ignores errors) To disable incoming federation of the
/// room, use --disable-federation
BanRoom {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`
BanListOfRooms {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
},
/// - Unbans a room to allow local users to join again
///
/// To re-enable incoming federation of the room, use --enable-federation
UnbanRoom {
#[arg(long)]
/// Enables incoming federation of the room after unbanning
enable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - List of all rooms we have banned
ListBannedRooms,
}
pub(super) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
RoomCommand::Alias(command) => room_alias_commands::process(command, body).await?,
RoomCommand::Directory(command) => room_directory_commands::process(command, body).await?,
RoomCommand::Moderation(command) => room_moderation_commands::process(command, body).await?,
RoomCommand::List {
page,
exclude_disabled,
exclude_banned,
} => list(body, page, exclude_disabled, exclude_banned).await?,
})
}
@@ -1,37 +1,77 @@
use api::client::leave_room; use api::client::leave_room;
use clap::Subcommand;
use conduit::{debug, error, info, warn, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId}; use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use tracing::{debug, error, info, warn};
use super::{super::Service, RoomModerationCommand}; use crate::{admin_command, admin_command_dispatch, get_room_info};
use crate::{get_room_info, services, user_is_local, Result};
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command_dispatch]
match command { #[derive(Debug, Subcommand)]
RoomModerationCommand::BanRoom { pub(crate) enum RoomModerationCommand {
force, /// - Bans a room from local users joining and evicts all our local users
room, /// from the room. Also blocks any invites (local and remote) for the
disable_federation, /// banned room.
} => ban_room(body, force, room, disable_federation).await, ///
RoomModerationCommand::BanListOfRooms { /// Server admins (users in the conduwuit admin room) will not be evicted
force, /// and server admins can still join the room. To evict admins too, use
disable_federation, /// --force (also ignores errors) To disable incoming federation of the
} => ban_list_of_rooms(body, force, disable_federation).await, /// room, use --disable-federation
RoomModerationCommand::UnbanRoom { BanRoom {
room, #[arg(short, long)]
enable_federation, /// Evicts admins out of the room and ignores any potential errors when
} => unban_room(body, room, enable_federation).await, /// making our local users leave the room
RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await, force: bool,
}
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`
BanListOfRooms {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
},
/// - Unbans a room to allow local users to join again
///
/// To re-enable incoming federation of the room, use --enable-federation
UnbanRoom {
#[arg(long)]
/// Enables incoming federation of the room after unbanning
enable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - List of all rooms we have banned
ListBannedRooms,
} }
#[admin_command]
async fn ban_room( async fn ban_room(
_body: Vec<&str>, force: bool, room: Box<RoomOrAliasId>, disable_federation: bool, &self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
debug!("Got room alias or ID: {}", room); debug!("Got room alias or ID: {}", room);
let admin_room_alias = &services().globals.admin_alias; let admin_room_alias = &self.services.globals.admin_alias;
if let Some(admin_room_id) = Service::get_admin_room()? { if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) { if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room.")); return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
} }
@@ -50,7 +90,7 @@ async fn ban_room(
debug!("Room specified is a room ID, banning room ID"); debug!("Room specified is a room ID, banning room ID");
services().rooms.metadata.ban_room(&room_id, true)?; self.services.rooms.metadata.ban_room(&room_id, true)?;
room_id room_id
} else if room.is_room_alias_id() { } else if room.is_room_alias_id() {
@@ -69,12 +109,13 @@ async fn ban_room(
get_alias_helper to fetch room ID remotely" get_alias_helper to fetch room ID remotely"
); );
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation"); debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@@ -92,7 +133,7 @@ async fn ban_room(
} }
}; };
services().rooms.metadata.ban_room(&room_id, true)?; self.services.rooms.metadata.ban_room(&room_id, true)?;
room_id room_id
} else { } else {
@@ -104,20 +145,21 @@ async fn ban_room(
debug!("Making all users leave the room {}", &room); debug!("Making all users leave the room {}", &room);
if force { if force {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
user_is_local(local_user) self.services.globals.user_is_local(local_user)
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would // who are in the admin room to the list of local users (would
// fail auth check) // fail auth check)
&& (user_is_local(local_user) && (self.services.globals.user_is_local(local_user)
// since this is a force operation, assume user is an admin // since this is a force operation, assume user is an admin
// if somehow this fails // if somehow this fails
&& services() && self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(true)) .unwrap_or(true))
@@ -128,30 +170,31 @@ async fn ban_room(
&local_user, &room_id &local_user, &room_id
); );
if let Err(e) = leave_room(&local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
warn!(%e, "Failed to leave room"); warn!(%e, "Failed to leave room");
} }
} }
} else { } else {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check) // who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
&& !services() && !self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(false)) .unwrap_or(false))
}) })
}) { }) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id); debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
error!( error!(
"Error attempting to make local user {} leave room {} during room banning: {}", "Error attempting to make local user {} leave room {} during room banning: {}",
&local_user, &room_id, e &local_user, &room_id, e
@@ -166,7 +209,7 @@ async fn ban_room(
} }
if disable_federation { if disable_federation {
services().rooms.metadata.disable_room(&room_id, true)?; self.services.rooms.metadata.disable_room(&room_id, true)?;
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Room banned, removed all our local users, and disabled incoming federation with room.", "Room banned, removed all our local users, and disabled incoming federation with room.",
)); ));
@@ -178,19 +221,22 @@ async fn ban_room(
)) ))
} }
async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let rooms_s = body let rooms_s = self
.clone() .body
.drain(1..body.len().saturating_sub(1)) .to_vec()
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let admin_room_alias = &services().globals.admin_alias; let admin_room_alias = &self.services.globals.admin_alias;
let mut room_ban_count: usize = 0; let mut room_ban_count: usize = 0;
let mut room_ids: Vec<OwnedRoomId> = Vec::new(); let mut room_ids: Vec<OwnedRoomId> = Vec::new();
@@ -198,7 +244,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
for &room in &rooms_s { for &room in &rooms_s {
match <&RoomOrAliasId>::try_from(room) { match <&RoomOrAliasId>::try_from(room) {
Ok(room_alias_or_id) => { Ok(room_alias_or_id) => {
if let Some(admin_room_id) = Service::get_admin_room()? { if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) { if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
info!("User specified admin room in bulk ban list, ignoring"); info!("User specified admin room in bulk ban list, ignoring");
continue; continue;
@@ -231,7 +277,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
match RoomAliasId::parse(room_alias_or_id) { match RoomAliasId::parse(room_alias_or_id) {
Ok(room_alias) => { Ok(room_alias) => {
let room_id = let room_id =
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!( debug!(
@@ -239,7 +285,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
ID over federation" ID over federation"
); );
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@@ -303,28 +350,35 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
for room_id in room_ids { for room_id in room_ids {
if services().rooms.metadata.ban_room(&room_id, true).is_ok() { if self
.services
.rooms
.metadata
.ban_room(&room_id, true)
.is_ok()
{
debug!("Banned {room_id} successfully"); debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1); room_ban_count = room_ban_count.saturating_add(1);
} }
debug!("Making all users leave the room {}", &room_id); debug!("Making all users leave the room {}", &room_id);
if force { if force {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote // additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local // users who are in the admin room to the list of local
// users (would fail auth check) // users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
// since this is a force operation, assume user is an // since this is a force operation, assume user is an
// admin if somehow this fails // admin if somehow this fails
&& services() && self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(true)) .unwrap_or(true))
@@ -334,31 +388,32 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)", "Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, room_id &local_user, room_id
); );
if let Err(e) = leave_room(&local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
warn!(%e, "Failed to leave room"); warn!(%e, "Failed to leave room");
} }
} }
} else { } else {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote // additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local // users who are in the admin room to the list of local
// users (would fail auth check) // users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
&& !services() && !self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(false)) .unwrap_or(false))
}) })
}) { }) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id); debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
error!( error!(
"Error attempting to make local user {} leave room {} during bulk room banning: {}", "Error attempting to make local user {} leave room {} during bulk room banning: {}",
&local_user, &room_id, e &local_user, &room_id, e
@@ -374,7 +429,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
if disable_federation { if disable_federation {
services().rooms.metadata.disable_room(&room_id, true)?; self.services.rooms.metadata.disable_room(&room_id, true)?;
} }
} }
@@ -390,9 +445,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
} }
async fn unban_room( #[admin_command]
_body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool, async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
let room_id = if room.is_room_id() { let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) { let room_id = match RoomId::parse(&room) {
Ok(room_id) => room_id, Ok(room_id) => room_id,
@@ -406,7 +460,7 @@ async fn unban_room(
debug!("Room specified is a room ID, unbanning room ID"); debug!("Room specified is a room ID, unbanning room ID");
services().rooms.metadata.ban_room(&room_id, false)?; self.services.rooms.metadata.ban_room(&room_id, false)?;
room_id room_id
} else if room.is_room_alias_id() { } else if room.is_room_alias_id() {
@@ -425,12 +479,13 @@ async fn unban_room(
get_alias_helper to fetch room ID remotely" get_alias_helper to fetch room ID remotely"
); );
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation"); debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@@ -448,7 +503,7 @@ async fn unban_room(
} }
}; };
services().rooms.metadata.ban_room(&room_id, false)?; self.services.rooms.metadata.ban_room(&room_id, false)?;
room_id room_id
} else { } else {
@@ -459,7 +514,7 @@ async fn unban_room(
}; };
if enable_federation { if enable_federation {
services().rooms.metadata.disable_room(&room_id, false)?; self.services.rooms.metadata.disable_room(&room_id, false)?;
return Ok(RoomMessageEventContent::text_plain("Room unbanned.")); return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
} }
@@ -469,8 +524,10 @@ async fn unban_room(
)) ))
} }
async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let rooms = services() async fn list_banned_rooms(&self) -> Result<RoomMessageEventContent> {
let rooms = self
.services
.rooms .rooms
.metadata .metadata
.list_banned_rooms() .list_banned_rooms()
@@ -484,7 +541,7 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
let mut rooms = room_ids let mut rooms = room_ids
.into_iter() .into_iter()
.map(|room_id| get_room_info(&room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);
rooms.reverse(); rooms.reverse();
+90 -34
View File
@@ -1,10 +1,14 @@
use conduit::{utils::time, warn, Err, Result}; use std::{fmt::Write, sync::Arc};
use conduit::{info, utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::services; use crate::admin_command;
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let elapsed = services() pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
let elapsed = self
.services
.server .server
.started .started
.elapsed() .elapsed()
@@ -14,14 +18,58 @@ pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent>
Ok(RoomMessageEventContent::notice_plain(format!("{result}."))) Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
} }
pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
// Construct and send the response // Construct and send the response
Ok(RoomMessageEventContent::text_plain(format!("{}", services().globals.config))) Ok(RoomMessageEventContent::text_plain(format!("{}", self.services.globals.config)))
} }
pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let services_usage = services().memory_usage().await?; pub(super) async fn list_features(
let database_usage = services().db.db.memory_usage()?; &self, available: bool, enabled: bool, comma: bool,
) -> Result<RoomMessageEventContent> {
let delim = if comma {
","
} else {
" "
};
if enabled && !available {
let features = info::rustc::features().join(delim);
let out = format!("`\n{features}\n`");
return Ok(RoomMessageEventContent::text_markdown(out));
}
if available && !enabled {
let features = info::cargo::features().join(delim);
let out = format!("`\n{features}\n`");
return Ok(RoomMessageEventContent::text_markdown(out));
}
let mut features = String::new();
let enabled = info::rustc::features();
let available = info::cargo::features();
for feature in available {
let active = enabled.contains(&feature.as_str());
let emoji = if active {
""
} else {
""
};
let remark = if active {
"[enabled]"
} else {
""
};
writeln!(features, "{emoji} {feature} {remark}")?;
}
Ok(RoomMessageEventContent::text_markdown(features))
}
#[admin_command]
pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> {
let services_usage = self.services.memory_usage().await?;
let database_usage = self.services.db.db.memory_usage()?;
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}")); let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@@ -29,14 +77,16 @@ pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventCon
))) )))
} }
pub(super) async fn clear_caches(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
services().clear_cache().await; pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
self.services.clear_cache().await;
Ok(RoomMessageEventContent::text_plain("Done.")) Ok(RoomMessageEventContent::text_plain("Done."))
} }
pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let result = services().globals.db.backup_list()?; pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.backup_list()?;
if result.is_empty() { if result.is_empty() {
Ok(RoomMessageEventContent::text_plain("No backups found.")) Ok(RoomMessageEventContent::text_plain("No backups found."))
@@ -45,46 +95,51 @@ pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventCon
} }
} }
pub(super) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let mut result = services() pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
let globals = Arc::clone(&self.services.globals);
let mut result = self
.services
.server .server
.runtime() .runtime()
.spawn_blocking(move || match services().globals.db.backup() { .spawn_blocking(move || match globals.db.backup() {
Ok(()) => String::new(), Ok(()) => String::new(),
Err(e) => (*e).to_string(), Err(e) => (*e).to_string(),
}) })
.await .await?;
.unwrap();
if result.is_empty() { if result.is_empty() {
result = services().globals.db.backup_list()?; result = self.services.globals.db.backup_list()?;
} }
Ok(RoomMessageEventContent::text_plain(&result))
}
pub(super) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result)) Ok(RoomMessageEventContent::notice_markdown(result))
} }
pub(super) async fn admin_notice(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn list_database_files(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result))
}
#[admin_command]
pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" "); let message = message.join(" ");
services().admin.send_text(&message).await; self.services.admin.send_text(&message).await;
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins")) Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
} }
#[cfg(conduit_mods)] #[admin_command]
pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
services().server.reload()?; self.services.server.reload()?;
Ok(RoomMessageEventContent::notice_plain("Reloading server...")) Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
} }
#[admin_command]
#[cfg(unix)] #[cfg(unix)]
pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessageEventContent> { pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
use conduit::utils::sys::current_exe_deleted; use conduit::utils::sys::current_exe_deleted;
if !force && current_exe_deleted() { if !force && current_exe_deleted() {
@@ -94,14 +149,15 @@ pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessage
); );
} }
services().server.restart()?; self.services.server.restart()?;
Ok(RoomMessageEventContent::notice_plain("Restarting server...")) Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
} }
pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> {
warn!("shutdown command"); warn!("shutdown command");
services().server.shutdown()?; self.services.server.shutdown()?;
Ok(RoomMessageEventContent::notice_plain("Shutting down server...")) Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
} }
+17 -28
View File
@@ -2,12 +2,11 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum ServerCommand { pub(super) enum ServerCommand {
/// - Time elapsed since startup /// - Time elapsed since startup
Uptime, Uptime,
@@ -15,6 +14,18 @@ pub(super) enum ServerCommand {
/// - Show configuration values /// - Show configuration values
ShowConfig, ShowConfig,
/// - List the features built into the server
ListFeatures {
#[arg(short, long)]
available: bool,
#[arg(short, long)]
enabled: bool,
#[arg(short, long)]
comma: bool,
},
/// - Print database memory usage statistics /// - Print database memory usage statistics
MemoryUsage, MemoryUsage,
@@ -36,9 +47,9 @@ pub(super) enum ServerCommand {
message: Vec<String>, message: Vec<String>,
}, },
#[cfg(conduit_mods)]
/// - Hot-reload the server /// - Hot-reload the server
Reload, #[clap(alias = "reload")]
ReloadMods,
#[cfg(unix)] #[cfg(unix)]
/// - Restart the server /// - Restart the server
@@ -50,25 +61,3 @@ pub(super) enum ServerCommand {
/// - Shutdown the server /// - Shutdown the server
Shutdown, Shutdown,
} }
pub(super) async fn process(command: ServerCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
ServerCommand::Uptime => uptime(body).await?,
ServerCommand::ShowConfig => show_config(body).await?,
ServerCommand::MemoryUsage => memory_usage(body).await?,
ServerCommand::ClearCaches => clear_caches(body).await?,
ServerCommand::ListBackups => list_backups(body).await?,
ServerCommand::BackupDatabase => backup_database(body).await?,
ServerCommand::ListDatabaseFiles => list_database_files(body).await?,
ServerCommand::AdminNotice {
message,
} => admin_notice(body, message).await?,
#[cfg(conduit_mods)]
ServerCommand::Reload => reload(body).await?,
#[cfg(unix)]
ServerCommand::Restart {
force,
} => restart(body, force).await?,
ServerCommand::Shutdown => shutdown(body).await?,
})
}
+1 -1
View File
@@ -12,7 +12,7 @@ fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) { fn get_help_inner(input: &str) {
use clap::Parser; use clap::Parser;
use crate::handler::AdminCommand; use crate::admin::AdminCommand;
let Err(error) = AdminCommand::try_parse_from(["argv[0] doesn't matter", input]) else { let Err(error) = AdminCommand::try_parse_from(["argv[0] doesn't matter", input]) else {
panic!("no error!"); panic!("no error!");
+113 -81
View File
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, fmt::Write as _}; use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname}; use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
use conduit::{utils, Result}; use conduit::{error, info, utils, warn, Result};
use ruma::{ use ruma::{
events::{ events::{
room::message::RoomMessageEventContent, room::message::RoomMessageEventContent,
@@ -10,17 +10,17 @@ use ruma::{
}, },
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
}; };
use tracing::{error, info, warn};
use crate::{ use crate::{
escape_html, get_room_info, services, admin_command, escape_html, get_room_info,
utils::{parse_active_local_user_id, parse_local_user_id}, utils::{parse_active_local_user_id, parse_local_user_id},
}; };
const AUTO_GEN_PASSWORD_LENGTH: usize = 25; const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
match services().users.list_local_users() { pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
match self.services.users.list_local_users() {
Ok(users) => { Ok(users) => {
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len()); let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
plain_msg += users.join("\n").as_str(); plain_msg += users.join("\n").as_str();
@@ -32,43 +32,51 @@ pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
} }
} }
pub(super) async fn create( #[admin_command]
_body: Vec<&str>, username: String, password: Option<String>, pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(&username)?; let user_id = parse_local_user_id(self.services, &username)?;
if services().users.exists(&user_id)? { if self.services.users.exists(&user_id)? {
return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists"))); return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
} }
if user_id.is_historical() {
return Ok(RoomMessageEventContent::text_plain(format!(
"User ID {user_id} does not conform to new Matrix identifier spec"
)));
}
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH)); let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
// Create user // Create user
services().users.create(&user_id, Some(password.as_str()))?; self.services
.users
.create(&user_id, Some(password.as_str()))?;
// Default to pretty displayname // Default to pretty displayname
let mut displayname = user_id.localpart().to_owned(); let mut displayname = user_id.localpart().to_owned();
// If `new_user_displayname_suffix` is set, registration will push whatever // If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it // content is set to the user's display name with a space before it
if !services() if !self
.services
.globals .globals
.config .config
.new_user_displayname_suffix .new_user_displayname_suffix
.is_empty() .is_empty()
{ {
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix) write!(displayname, " {}", self.services.globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer"); .expect("should be able to write to string buffer");
} }
services() self.services
.users .users
.set_displayname(&user_id, Some(displayname)) .set_displayname(&user_id, Some(displayname))
.await?; .await?;
// Initial account data // Initial account data
services().account_data.update( self.services.account_data.update(
None, None,
&user_id, &user_id,
ruma::events::GlobalAccountDataEventType::PushRules ruma::events::GlobalAccountDataEventType::PushRules
@@ -82,12 +90,13 @@ pub(super) async fn create(
.expect("to json value always works"), .expect("to json value always works"),
)?; )?;
if !services().globals.config.auto_join_rooms.is_empty() { if !self.services.globals.config.auto_join_rooms.is_empty() {
for room in &services().globals.config.auto_join_rooms { for room in &self.services.globals.config.auto_join_rooms {
if !services() if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(services().globals.server_name(), room)? .server_in_room(self.services.globals.server_name(), room)?
{ {
warn!("Skipping room {room} to automatically join as we have never joined before."); warn!("Skipping room {room} to automatically join as we have never joined before.");
continue; continue;
@@ -95,10 +104,11 @@ pub(super) async fn create(
if let Some(room_id_server_name) = room.server_name() { if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper( match join_room_by_id_helper(
self.services,
&user_id, &user_id,
room, room,
Some("Automatically joining this room upon registration".to_owned()), Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()], &[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()],
None, None,
) )
.await .await
@@ -123,38 +133,38 @@ pub(super) async fn create(
))) )))
} }
pub(super) async fn deactivate( #[admin_command]
_body: Vec<&str>, no_leave_rooms: bool, user_id: String, pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(&user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
// don't deactivate the server service account // don't deactivate the server service account
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to deactivate the server service account.", "Not allowed to deactivate the server service account.",
)); ));
} }
services().users.deactivate_account(&user_id)?; self.services.users.deactivate_account(&user_id)?;
if !no_leave_rooms { if !no_leave_rooms {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"Making {user_id} leave all rooms after deactivation..." "Making {user_id} leave all rooms after deactivation..."
))) )))
.await; .await;
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?; update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?; update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(&user_id).await; leave_all_rooms(self.services, &user_id).await;
} }
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@@ -162,10 +172,11 @@ pub(super) async fn deactivate(
))) )))
} }
pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> { #[admin_command]
let user_id = parse_local_user_id(&username)?; pub(super) async fn reset_password(&self, username: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &username)?;
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to set the password for the server account. Please use the emergency password config option.", "Not allowed to set the password for the server account. Please use the emergency password config option.",
)); ));
@@ -173,7 +184,8 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH); let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
match services() match self
.services
.users .users
.set_password(&user_id, Some(new_password.as_str())) .set_password(&user_id, Some(new_password.as_str()))
{ {
@@ -186,28 +198,29 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
} }
} }
pub(super) async fn deactivate_all( #[admin_command]
body: Vec<&str>, no_leave_rooms: bool, force: bool, pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let usernames = body let usernames = self
.clone() .body
.drain(1..body.len().saturating_sub(1)) .to_vec()
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len()); let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
let mut admins = Vec::new(); let mut admins = Vec::new();
for username in usernames { for username in usernames {
match parse_active_local_user_id(username) { match parse_active_local_user_id(self.services, username) {
Ok(user_id) => { Ok(user_id) => {
if services().users.is_admin(&user_id)? && !force { if self.services.users.is_admin(&user_id)? && !force {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is an admin and --force is not set, skipping over" "{username} is an admin and --force is not set, skipping over"
@@ -218,8 +231,8 @@ pub(super) async fn deactivate_all(
} }
// don't deactivate the server service account // don't deactivate the server service account
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is the server service account, skipping over" "{username} is the server service account, skipping over"
@@ -231,7 +244,7 @@ pub(super) async fn deactivate_all(
user_ids.push(user_id); user_ids.push(user_id);
}, },
Err(e) => { Err(e) => {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is not a valid username, skipping over: {e}" "{username} is not a valid username, skipping over: {e}"
@@ -245,24 +258,25 @@ pub(super) async fn deactivate_all(
let mut deactivation_count: usize = 0; let mut deactivation_count: usize = 0;
for user_id in user_ids { for user_id in user_ids {
match services().users.deactivate_account(&user_id) { match self.services.users.deactivate_account(&user_id) {
Ok(()) => { Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1); deactivation_count = deactivation_count.saturating_add(1);
if !no_leave_rooms { if !no_leave_rooms {
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all"); info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?; update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?; update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(&user_id).await; leave_all_rooms(self.services, &user_id).await;
} }
}, },
Err(e) => { Err(e) => {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}"))) .send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
.await; .await;
@@ -283,16 +297,18 @@ pub(super) async fn deactivate_all(
} }
} }
pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(&user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services() let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|room_id| get_room_info(&room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect(); .collect();
if rooms.is_empty() { if rooms.is_empty() {
@@ -334,41 +350,55 @@ pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
Ok(RoomMessageEventContent::text_html(output_plain, output_html)) Ok(RoomMessageEventContent::text_html(output_plain, output_html))
} }
#[admin_command]
pub(super) async fn force_join_room( pub(super) async fn force_join_room(
_body: Vec<&str>, user_id: String, room_id: OwnedRoomOrAliasId, &self, user_id: String, room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = services().rooms.alias.resolve(&room_id).await?; let room_id = self.services.rooms.alias.resolve(&room_id).await?;
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user"); assert!(
join_room_by_id_helper(&user_id, &room_id, None, &[], None).await?; self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None).await?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.", "{user_id} has been joined to {room_id}.",
))) )))
} }
pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> { #[admin_command]
let user_id = parse_local_user_id(&user_id)?; pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
let displayname = services() let user_id = parse_local_user_id(self.services, &user_id)?;
let displayname = self
.services
.users .users
.displayname(&user_id)? .displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string()); .unwrap_or_else(|| user_id.to_string());
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user"); assert!(
service::admin::make_user_admin(&user_id, displayname).await?; self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
self.services
.admin
.make_user_admin(&user_id, displayname)
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been granted admin privileges.", "{user_id} has been granted admin privileges.",
))) )))
} }
#[admin_command]
pub(super) async fn put_room_tag( pub(super) async fn put_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String, &self, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?; let user_id = parse_active_local_user_id(self.services, &user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
@@ -386,7 +416,7 @@ pub(super) async fn put_room_tag(
.tags .tags
.insert(tag.clone().into(), TagInfo::new()); .insert(tag.clone().into(), TagInfo::new());
services().account_data.update( self.services.account_data.update(
Some(&room_id), Some(&room_id),
&user_id, &user_id,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@@ -398,12 +428,14 @@ pub(super) async fn put_room_tag(
))) )))
} }
#[admin_command]
pub(super) async fn delete_room_tag( pub(super) async fn delete_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String, &self, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?; let user_id = parse_active_local_user_id(self.services, &user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
@@ -418,7 +450,7 @@ pub(super) async fn delete_room_tag(
tags_event.content.tags.remove(&tag.clone().into()); tags_event.content.tags.remove(&tag.clone().into());
services().account_data.update( self.services.account_data.update(
Some(&room_id), Some(&room_id),
&user_id, &user_id,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@@ -430,12 +462,12 @@ pub(super) async fn delete_room_tag(
))) )))
} }
pub(super) async fn get_room_tags( #[admin_command]
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let user_id = parse_active_local_user_id(self.services, &user_id)?;
let user_id = parse_active_local_user_id(&user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
+8 -51
View File
@@ -2,15 +2,16 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId, RoomId}; use ruma::{OwnedRoomOrAliasId, RoomId};
use self::commands::*; use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))] #[admin_command_dispatch]
#[derive(Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum UserCommand { pub(super) enum UserCommand {
/// - Create a new user /// - Create a new user
Create { #[clap(alias = "create")]
CreateUser {
/// Username of the new user /// Username of the new user
username: String, username: String,
/// Password of the new user, if unspecified one is generated /// Password of the new user, if unspecified one is generated
@@ -57,7 +58,8 @@ pub(super) enum UserCommand {
}, },
/// - List local users in the database /// - List local users in the database
List, #[clap(alias = "list")]
ListUsers,
/// - Lists all the rooms (local and remote) that the specified user is /// - Lists all the rooms (local and remote) that the specified user is
/// joined in /// joined in
@@ -102,48 +104,3 @@ pub(super) enum UserCommand {
room_id: Box<RoomId>, room_id: Box<RoomId>,
}, },
} }
pub(super) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
UserCommand::List => list(body).await?,
UserCommand::Create {
username,
password,
} => create(body, username, password).await?,
UserCommand::Deactivate {
no_leave_rooms,
user_id,
} => deactivate(body, no_leave_rooms, user_id).await?,
UserCommand::ResetPassword {
username,
} => reset_password(body, username).await?,
UserCommand::DeactivateAll {
no_leave_rooms,
force,
} => deactivate_all(body, no_leave_rooms, force).await?,
UserCommand::ListJoinedRooms {
user_id,
} => list_joined_rooms(body, user_id).await?,
UserCommand::ForceJoinRoom {
user_id,
room_id,
} => force_join_room(body, user_id, room_id).await?,
UserCommand::MakeUserAdmin {
user_id,
} => make_user_admin(body, user_id).await?,
UserCommand::PutRoomTag {
user_id,
room_id,
tag,
} => put_room_tag(body, user_id, room_id, tag).await?,
UserCommand::DeleteRoomTag {
user_id,
room_id,
tag,
} => delete_room_tag(body, user_id, room_id, tag).await?,
UserCommand::GetRoomTags {
user_id,
room_id,
} => get_room_tags(body, user_id, room_id).await?,
})
}
+14 -16
View File
@@ -1,8 +1,6 @@
use conduit_core::{err, Err}; use conduit_core::{err, Err, Result};
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId}; use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
use service::user_is_local; use service::Services;
use crate::{services, Result};
pub(crate) fn escape_html(s: &str) -> String { pub(crate) fn escape_html(s: &str) -> String {
s.replace('&', "&amp;") s.replace('&', "&amp;")
@@ -10,17 +8,17 @@ pub(crate) fn escape_html(s: &str) -> String {
.replace('>', "&gt;") .replace('>', "&gt;")
} }
pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) { pub(crate) fn get_room_info(services: &Services, id: &RoomId) -> (OwnedRoomId, u64, String) {
( (
id.into(), id.into(),
services() services
.rooms .rooms
.state_cache .state_cache
.room_joined_count(id) .room_joined_count(id)
.ok() .ok()
.flatten() .flatten()
.unwrap_or(0), .unwrap_or(0),
services() services
.rooms .rooms
.state_accessor .state_accessor
.get_name(id) .get_name(id)
@@ -31,16 +29,16 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
} }
/// Parses user ID /// Parses user ID
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> { pub(crate) fn parse_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name()) UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
.map_err(|e| err!("The supplied username is not a valid username: {e}")) .map_err(|e| err!("The supplied username is not a valid username: {e}"))
} }
/// Parses user ID as our local user /// Parses user ID as our local user
pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> { pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_user_id(user_id)?; let user_id = parse_user_id(services, user_id)?;
if !user_is_local(&user_id) { if !services.globals.user_is_local(&user_id) {
return Err!("User {user_id:?} does not belong to our server."); return Err!("User {user_id:?} does not belong to our server.");
} }
@@ -48,14 +46,14 @@ pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
} }
/// Parses user ID that is an active (not guest or deactivated) local user /// Parses user ID that is an active (not guest or deactivated) local user
pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> { pub(crate) fn parse_active_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_local_user_id(user_id)?; let user_id = parse_local_user_id(services, user_id)?;
if !services().users.exists(&user_id)? { if !services.users.exists(&user_id)? {
return Err!("User {user_id:?} does not exist on this server."); return Err!("User {user_id:?} does not exist on this server.");
} }
if services().users.is_deactivated(&user_id)? { if services.users.is_deactivated(&user_id)? {
return Err!("User {user_id:?} is deactivated."); return Err!("User {user_id:?} is deactivated.");
} }
+80 -77
View File
@@ -1,7 +1,8 @@
use std::fmt::Write; use std::fmt::Write;
use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::debug_info; use conduit::{debug_info, error, info, utils, warn, Error, Result};
use register::RegistrationKind; use register::RegistrationKind;
use ruma::{ use ruma::{
api::client::{ api::client::{
@@ -17,15 +18,9 @@ use ruma::{
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType}, events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
push, OwnedRoomId, UserId, push, OwnedRoomId, UserId,
}; };
use tracing::{error, info, warn};
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{ use crate::Ruma;
service::user_is_local,
services,
utils::{self},
Error, Result, Ruma,
};
const RANDOM_USER_ID_LENGTH: usize = 10; const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -42,20 +37,21 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
/// invalid when trying to register /// invalid when trying to register
#[tracing::instrument(skip_all, fields(%client), name = "register_available")] #[tracing::instrument(skip_all, fields(%client), name = "register_available")]
pub(crate) async fn get_register_available_route( pub(crate) async fn get_register_available_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_username_availability::v3::Request>,
) -> Result<get_username_availability::v3::Response> { ) -> Result<get_username_availability::v3::Response> {
// Validate user id // Validate user id
let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services().globals.server_name()) let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services.globals.server_name())
.ok() .ok()
.filter(|user_id| !user_id.is_historical() && user_is_local(user_id)) .filter(|user_id| !user_id.is_historical() && services.globals.user_is_local(user_id))
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?; .ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
// Check if username is creative enough // Check if username is creative enough
if services().users.exists(&user_id)? { if services.users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken.")); return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
} }
if services() if services
.globals .globals
.forbidden_usernames() .forbidden_usernames()
.is_match(user_id.localpart()) .is_match(user_id.localpart())
@@ -91,9 +87,9 @@ pub(crate) async fn get_register_available_route(
#[allow(clippy::doc_markdown)] #[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register")] #[tracing::instrument(skip_all, fields(%client), name = "register")]
pub(crate) async fn register_route( pub(crate) async fn register_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<register::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<register::v3::Request>,
) -> Result<register::v3::Response> { ) -> Result<register::v3::Response> {
if !services().globals.allow_registration() && body.appservice_info.is_none() { if !services.globals.allow_registration() && body.appservice_info.is_none() {
info!( info!(
"Registration disabled and request not from known appservice, rejecting registration attempt for username \ "Registration disabled and request not from known appservice, rejecting registration attempt for username \
{:?}", {:?}",
@@ -105,8 +101,8 @@ pub(crate) async fn register_route(
let is_guest = body.kind == RegistrationKind::Guest; let is_guest = body.kind == RegistrationKind::Guest;
if is_guest if is_guest
&& (!services().globals.allow_guest_registration() && (!services.globals.allow_guest_registration()
|| (services().globals.allow_registration() && services().globals.config.registration_token.is_some())) || (services.globals.allow_registration() && services.globals.config.registration_token.is_some()))
{ {
info!( info!(
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \ "Guest registration disabled / registration enabled with token configured, rejecting guest registration \
@@ -121,7 +117,7 @@ pub(crate) async fn register_route(
// forbid guests from registering if there is not a real admin user yet. give // forbid guests from registering if there is not a real admin user yet. give
// generic user error. // generic user error.
if is_guest && services().users.count()? < 2 { if is_guest && services.users.count()? < 2 {
warn!( warn!(
"Guest account attempted to register before a real admin user has been registered, rejecting \ "Guest account attempted to register before a real admin user has been registered, rejecting \
registration. Guest's initial device name: {:?}", registration. Guest's initial device name: {:?}",
@@ -133,16 +129,16 @@ pub(crate) async fn register_route(
let user_id = match (&body.username, is_guest) { let user_id = match (&body.username, is_guest) {
(Some(username), false) => { (Some(username), false) => {
let proposed_user_id = let proposed_user_id =
UserId::parse_with_server_name(username.to_lowercase(), services().globals.server_name()) UserId::parse_with_server_name(username.to_lowercase(), services.globals.server_name())
.ok() .ok()
.filter(|user_id| !user_id.is_historical() && user_is_local(user_id)) .filter(|user_id| !user_id.is_historical() && services.globals.user_is_local(user_id))
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?; .ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
if services().users.exists(&proposed_user_id)? { if services.users.exists(&proposed_user_id)? {
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken.")); return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
} }
if services() if services
.globals .globals
.forbidden_usernames() .forbidden_usernames()
.is_match(proposed_user_id.localpart()) .is_match(proposed_user_id.localpart())
@@ -155,10 +151,10 @@ pub(crate) async fn register_route(
_ => loop { _ => loop {
let proposed_user_id = UserId::parse_with_server_name( let proposed_user_id = UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(), utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
services().globals.server_name(), services.globals.server_name(),
) )
.unwrap(); .unwrap();
if !services().users.exists(&proposed_user_id)? { if !services.users.exists(&proposed_user_id)? {
break proposed_user_id; break proposed_user_id;
} }
}, },
@@ -172,13 +168,13 @@ pub(crate) async fn register_route(
} else { } else {
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing appservice token.")); return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing appservice token."));
} }
} else if services().appservice.is_exclusive_user_id(&user_id).await { } else if services.appservice.is_exclusive_user_id(&user_id).await {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice.")); return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice."));
} }
// UIAA // UIAA
let mut uiaainfo; let mut uiaainfo;
let skip_auth = if services().globals.config.registration_token.is_some() { let skip_auth = if services.globals.config.registration_token.is_some() {
// Registration token required // Registration token required
uiaainfo = UiaaInfo { uiaainfo = UiaaInfo {
flows: vec![AuthFlow { flows: vec![AuthFlow {
@@ -206,8 +202,8 @@ pub(crate) async fn register_route(
if !skip_auth { if !skip_auth {
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services().uiaa.try_auth( let (worked, uiaainfo) = services.uiaa.try_auth(
&UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid"), &UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(), "".into(),
auth, auth,
&uiaainfo, &uiaainfo,
@@ -218,8 +214,8 @@ pub(crate) async fn register_route(
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services().uiaa.create( services.uiaa.create(
&UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid"), &UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(), "".into(),
&uiaainfo, &uiaainfo,
&json, &json,
@@ -237,25 +233,25 @@ pub(crate) async fn register_route(
}; };
// Create user // Create user
services().users.create(&user_id, password)?; services.users.create(&user_id, password)?;
// Default to pretty displayname // Default to pretty displayname
let mut displayname = user_id.localpart().to_owned(); let mut displayname = user_id.localpart().to_owned();
// If `new_user_displayname_suffix` is set, registration will push whatever // If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it // content is set to the user's display name with a space before it
if !services().globals.new_user_displayname_suffix().is_empty() { if !services.globals.new_user_displayname_suffix().is_empty() {
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix) write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer"); .expect("should be able to write to string buffer");
} }
services() services
.users .users
.set_displayname(&user_id, Some(displayname.clone())) .set_displayname(&user_id, Some(displayname.clone()))
.await?; .await?;
// Initial account data // Initial account data
services().account_data.update( services.account_data.update(
None, None,
&user_id, &user_id,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -290,16 +286,20 @@ pub(crate) async fn register_route(
let token = utils::random_string(TOKEN_LENGTH); let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account // Create device for this account
services() services.users.create_device(
.users &user_id,
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?; &device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)?;
debug_info!(%user_id, %device_id, "User account was created"); debug_info!(%user_id, %device_id, "User account was created");
// log in conduit admin channel if a non-guest user registered // log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest { if body.appservice_info.is_none() && !is_guest {
info!("New user \"{user_id}\" registered on this server."); info!("New user \"{user_id}\" registered on this server.");
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client}." "New user \"{user_id}\" registered on this server from IP {client}."
@@ -308,7 +308,7 @@ pub(crate) async fn register_route(
} }
// log in conduit admin channel if a guest registered // log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() { if body.appservice_info.is_none() && is_guest && services.globals.log_guest_registrations() {
info!("New guest user \"{user_id}\" registered on this server."); info!("New guest user \"{user_id}\" registered on this server.");
if let Some(device_display_name) = &body.initial_device_display_name { if let Some(device_display_name) = &body.initial_device_display_name {
@@ -317,7 +317,7 @@ pub(crate) async fn register_route(
.as_ref() .as_ref()
.is_some_and(|device_display_name| !device_display_name.is_empty()) .is_some_and(|device_display_name| !device_display_name.is_empty())
{ {
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \ "Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
@@ -325,7 +325,7 @@ pub(crate) async fn register_route(
))) )))
.await; .await;
} else { } else {
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on this server from IP \ "Guest user \"{user_id}\" with no device display name registered on this server from IP \
@@ -334,7 +334,7 @@ pub(crate) async fn register_route(
.await; .await;
} }
} else { } else {
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on this server from IP {client}.", "Guest user \"{user_id}\" with no device display name registered on this server from IP {client}.",
@@ -346,14 +346,12 @@ pub(crate) async fn register_route(
// If this is the first real user, grant them admin privileges except for guest // If this is the first real user, grant them admin privileges except for guest
// users Note: the server user, @conduit:servername, is generated first // users Note: the server user, @conduit:servername, is generated first
if !is_guest { if !is_guest {
if let Some(admin_room) = service::admin::Service::get_admin_room()? { if let Some(admin_room) = services.admin.get_admin_room()? {
if services() if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
.rooms services
.state_cache .admin
.room_joined_count(&admin_room)? .make_user_admin(&user_id, displayname)
== Some(1) .await?;
{
service::admin::make_user_admin(&user_id, displayname).await?;
warn!("Granting {user_id} admin privileges as the first user"); warn!("Granting {user_id} admin privileges as the first user");
} }
@@ -361,14 +359,14 @@ pub(crate) async fn register_route(
} }
if body.appservice_info.is_none() if body.appservice_info.is_none()
&& !services().globals.config.auto_join_rooms.is_empty() && !services.globals.config.auto_join_rooms.is_empty()
&& (services().globals.allow_guests_auto_join_rooms() || !is_guest) && (services.globals.allow_guests_auto_join_rooms() || !is_guest)
{ {
for room in &services().globals.config.auto_join_rooms { for room in &services.globals.config.auto_join_rooms {
if !services() if !services
.rooms .rooms
.state_cache .state_cache
.server_in_room(services().globals.server_name(), room)? .server_in_room(services.globals.server_name(), room)?
{ {
warn!("Skipping room {room} to automatically join as we have never joined before."); warn!("Skipping room {room} to automatically join as we have never joined before.");
continue; continue;
@@ -376,10 +374,11 @@ pub(crate) async fn register_route(
if let Some(room_id_server_name) = room.server_name() { if let Some(room_id_server_name) = room.server_name() {
if let Err(e) = join_room_by_id_helper( if let Err(e) = join_room_by_id_helper(
&services,
&user_id, &user_id,
room, room,
Some("Automatically joining this room upon registration".to_owned()), Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()], &[room_id_server_name.to_owned(), services.globals.server_name().to_owned()],
None, None,
) )
.await .await
@@ -421,7 +420,8 @@ pub(crate) async fn register_route(
/// - Triggers device list updates /// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "change_password")] #[tracing::instrument(skip_all, fields(%client), name = "change_password")]
pub(crate) async fn change_password_route( pub(crate) async fn change_password_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<change_password::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> { ) -> Result<change_password::v3::Response> {
// Authentication for this endpoint was made optional, but we need // Authentication for this endpoint was made optional, but we need
// authentication currently // authentication currently
@@ -442,7 +442,7 @@ pub(crate) async fn change_password_route(
}; };
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services() let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?; .try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked { if !worked {
@@ -451,7 +451,7 @@ pub(crate) async fn change_password_route(
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services() services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
@@ -459,24 +459,24 @@ pub(crate) async fn change_password_route(
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
} }
services() services
.users .users
.set_password(sender_user, Some(&body.new_password))?; .set_password(sender_user, Some(&body.new_password))?;
if body.logout_devices { if body.logout_devices {
// Logout all devices except the current one // Logout all devices except the current one
for id in services() for id in services
.users .users
.all_device_ids(sender_user) .all_device_ids(sender_user)
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|id| id != sender_device) .filter(|id| id != sender_device)
{ {
services().users.remove_device(sender_user, &id)?; services.users.remove_device(sender_user, &id)?;
} }
} }
info!("User {sender_user} changed their password."); info!("User {sender_user} changed their password.");
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} changed their password." "User {sender_user} changed their password."
@@ -491,14 +491,16 @@ pub(crate) async fn change_password_route(
/// Get `user_id` of the sender user. /// Get `user_id` of the sender user.
/// ///
/// Note: Also works for Application Services /// Note: Also works for Application Services
pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3::Response> { pub(crate) async fn whoami_route(
State(services): State<crate::State>, body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device_id = body.sender_device.clone(); let device_id = body.sender_device.clone();
Ok(whoami::v3::Response { Ok(whoami::v3::Response {
user_id: sender_user.clone(), user_id: sender_user.clone(),
device_id, device_id,
is_guest: services().users.is_deactivated(sender_user)? && body.appservice_info.is_none(), is_guest: services.users.is_deactivated(sender_user)? && body.appservice_info.is_none(),
}) })
} }
@@ -515,7 +517,8 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
/// - Removes ability to log in again /// - Removes ability to log in again
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")] #[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
pub(crate) async fn deactivate_route( pub(crate) async fn deactivate_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<deactivate::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> { ) -> Result<deactivate::v3::Response> {
// Authentication for this endpoint was made optional, but we need // Authentication for this endpoint was made optional, but we need
// authentication currently // authentication currently
@@ -536,7 +539,7 @@ pub(crate) async fn deactivate_route(
}; };
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services() let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?; .try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked { if !worked {
@@ -545,7 +548,7 @@ pub(crate) async fn deactivate_route(
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services() services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
@@ -554,23 +557,23 @@ pub(crate) async fn deactivate_route(
} }
// Remove devices and mark account as deactivated // Remove devices and mark account as deactivated
services().users.deactivate_account(sender_user)?; services.users.deactivate_account(sender_user)?;
// Remove profile pictures and display name // Remove profile pictures and display name
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(sender_user)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
super::update_displayname(sender_user.clone(), None, all_joined_rooms.clone()).await?; super::update_displayname(&services, sender_user.clone(), None, all_joined_rooms.clone()).await?;
super::update_avatar_url(sender_user.clone(), None, None, all_joined_rooms).await?; super::update_avatar_url(&services, sender_user.clone(), None, None, all_joined_rooms).await?;
// Make the user leave all rooms before deactivation // Make the user leave all rooms before deactivation
super::leave_all_rooms(sender_user).await; super::leave_all_rooms(&services, sender_user).await;
info!("User {sender_user} deactivated their account."); info!("User {sender_user} deactivated their account.");
services() services
.admin .admin
.send_message(RoomMessageEventContent::notice_plain(format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} deactivated their account." "User {sender_user} deactivated their account."
@@ -632,9 +635,9 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
/// Currently does not have any ratelimiting, and this isn't very practical as /// Currently does not have any ratelimiting, and this isn't very practical as
/// there is only one registration token allowed. /// there is only one registration token allowed.
pub(crate) async fn check_registration_token_validity( pub(crate) async fn check_registration_token_validity(
body: Ruma<check_registration_token_validity::v1::Request>, State(services): State<crate::State>, body: Ruma<check_registration_token_validity::v1::Request>,
) -> Result<check_registration_token_validity::v1::Response> { ) -> Result<check_registration_token_validity::v1::Response> {
let Some(reg_token) = services().globals.config.registration_token.clone() else { let Some(reg_token) = services.globals.config.registration_token.clone() else {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Server does not allow token registration.", "Server does not allow token registration.",
+34 -18
View File
@@ -1,3 +1,5 @@
use axum::extract::State;
use conduit::{debug, Error, Result};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use ruma::{ use ruma::{
api::client::{ api::client::{
@@ -6,21 +8,27 @@ use ruma::{
}, },
OwnedServerName, RoomAliasId, RoomId, OwnedServerName, RoomAliasId, RoomId,
}; };
use tracing::debug; use service::Services;
use crate::{service::server_is_ours, services, Error, Result, Ruma}; use crate::Ruma;
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}` /// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
/// ///
/// Creates a new room alias on this server. /// Creates a new room alias on this server.
pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result<create_alias::v3::Response> { pub(crate) async fn create_alias_route(
State(services): State<crate::State>, body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
service::rooms::alias::appservice_checks(&body.room_alias, &body.appservice_info).await?; services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
// this isn't apart of alias_checks or delete alias route because we should // this isn't apart of alias_checks or delete alias route because we should
// allow removing forbidden room aliases // allow removing forbidden room aliases
if services() if services
.globals .globals
.forbidden_alias_names() .forbidden_alias_names()
.is_match(body.room_alias.alias()) .is_match(body.room_alias.alias())
@@ -28,7 +36,7 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room alias is forbidden.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Room alias is forbidden."));
} }
if services() if services
.rooms .rooms
.alias .alias
.resolve_local_alias(&body.room_alias)? .resolve_local_alias(&body.room_alias)?
@@ -37,7 +45,7 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
return Err(Error::Conflict("Alias already exists.")); return Err(Error::Conflict("Alias already exists."));
} }
services() services
.rooms .rooms
.alias .alias
.set_alias(&body.room_alias, &body.room_id, sender_user)?; .set_alias(&body.room_alias, &body.room_id, sender_user)?;
@@ -50,12 +58,18 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
/// Deletes a room alias from this server. /// Deletes a room alias from this server.
/// ///
/// - TODO: Update canonical alias event /// - TODO: Update canonical alias event
pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) -> Result<delete_alias::v3::Response> { pub(crate) async fn delete_alias_route(
State(services): State<crate::State>, body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
service::rooms::alias::appservice_checks(&body.room_alias, &body.appservice_info).await?; services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
if services() if services
.rooms .rooms
.alias .alias
.resolve_local_alias(&body.room_alias)? .resolve_local_alias(&body.room_alias)?
@@ -64,7 +78,7 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist."));
} }
services() services
.rooms .rooms
.alias .alias
.remove_alias(&body.room_alias, sender_user) .remove_alias(&body.room_alias, sender_user)
@@ -78,11 +92,13 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
/// # `GET /_matrix/client/v3/directory/room/{roomAlias}` /// # `GET /_matrix/client/v3/directory/room/{roomAlias}`
/// ///
/// Resolve an alias locally or over federation. /// Resolve an alias locally or over federation.
pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Result<get_alias::v3::Response> { pub(crate) async fn get_alias_route(
State(services): State<crate::State>, body: Ruma<get_alias::v3::Request>,
) -> Result<get_alias::v3::Response> {
let room_alias = body.body.room_alias; let room_alias = body.body.room_alias;
let servers = None; let servers = None;
let Ok((room_id, pre_servers)) = services() let Ok((room_id, pre_servers)) = services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, servers.as_ref()) .resolve_alias(&room_alias, servers.as_ref())
@@ -91,17 +107,17 @@ pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Resul
return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found."));
}; };
let servers = room_available_servers(&room_id, &room_alias, &pre_servers); let servers = room_available_servers(&services, &room_id, &room_alias, &pre_servers);
debug!(?room_alias, ?room_id, "available servers: {servers:?}"); debug!(?room_alias, ?room_id, "available servers: {servers:?}");
Ok(get_alias::v3::Response::new(room_id, servers)) Ok(get_alias::v3::Response::new(room_id, servers))
} }
fn room_available_servers( fn room_available_servers(
room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>, services: &Services, room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
) -> Vec<OwnedServerName> { ) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest // find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services() let mut servers: Vec<OwnedServerName> = services
.rooms .rooms
.state_cache .state_cache
.room_servers(room_id) .room_servers(room_id)
@@ -124,10 +140,10 @@ fn room_available_servers(
// prefer the room alias server first // prefer the room alias server first
if let Some(server_index) = servers if let Some(server_index) = servers
.iter() .iter()
.position(|server_name| server_is_ours(server_name)) .position(|server_name| services.globals.server_is_ours(server_name))
{ {
servers.swap_remove(server_index); servers.swap_remove(server_index);
servers.insert(0, services().globals.server_name().to_owned()); servers.insert(0, services.globals.server_name().to_owned());
} else if let Some(alias_server_index) = servers } else if let Some(alias_server_index) = servers
.iter() .iter()
.position(|server| server == room_alias.server_name()) .position(|server| server == room_alias.server_name())
+49 -62
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{ api::client::{
backup::{ backup::{
@@ -11,16 +12,16 @@ use ruma::{
UInt, UInt,
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `POST /_matrix/client/r0/room_keys/version` /// # `POST /_matrix/client/r0/room_keys/version`
/// ///
/// Creates a new backup. /// Creates a new backup.
pub(crate) async fn create_backup_version_route( pub(crate) async fn create_backup_version_route(
body: Ruma<create_backup_version::v3::Request>, State(services): State<crate::State>, body: Ruma<create_backup_version::v3::Request>,
) -> Result<create_backup_version::v3::Response> { ) -> Result<create_backup_version::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let version = services() let version = services
.key_backups .key_backups
.create_backup(sender_user, &body.algorithm)?; .create_backup(sender_user, &body.algorithm)?;
@@ -34,10 +35,10 @@ pub(crate) async fn create_backup_version_route(
/// Update information about an existing backup. Only `auth_data` can be /// Update information about an existing backup. Only `auth_data` can be
/// modified. /// modified.
pub(crate) async fn update_backup_version_route( pub(crate) async fn update_backup_version_route(
body: Ruma<update_backup_version::v3::Request>, State(services): State<crate::State>, body: Ruma<update_backup_version::v3::Request>,
) -> Result<update_backup_version::v3::Response> { ) -> Result<update_backup_version::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services
.key_backups .key_backups
.update_backup(sender_user, &body.version, &body.algorithm)?; .update_backup(sender_user, &body.version, &body.algorithm)?;
@@ -48,20 +49,20 @@ pub(crate) async fn update_backup_version_route(
/// ///
/// Get information about the latest backup version. /// Get information about the latest backup version.
pub(crate) async fn get_latest_backup_info_route( pub(crate) async fn get_latest_backup_info_route(
body: Ruma<get_latest_backup_info::v3::Request>, State(services): State<crate::State>, body: Ruma<get_latest_backup_info::v3::Request>,
) -> Result<get_latest_backup_info::v3::Response> { ) -> Result<get_latest_backup_info::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let (version, algorithm) = services() let (version, algorithm) = services
.key_backups .key_backups
.get_latest_backup(sender_user)? .get_latest_backup(sender_user)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
Ok(get_latest_backup_info::v3::Response { Ok(get_latest_backup_info::v3::Response {
algorithm, algorithm,
count: (UInt::try_from(services().key_backups.count_keys(sender_user, &version)?) count: (UInt::try_from(services.key_backups.count_keys(sender_user, &version)?)
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services().key_backups.get_etag(sender_user, &version)?, etag: services.key_backups.get_etag(sender_user, &version)?,
version, version,
}) })
} }
@@ -70,10 +71,10 @@ pub(crate) async fn get_latest_backup_info_route(
/// ///
/// Get information about an existing backup. /// Get information about an existing backup.
pub(crate) async fn get_backup_info_route( pub(crate) async fn get_backup_info_route(
body: Ruma<get_backup_info::v3::Request>, State(services): State<crate::State>, body: Ruma<get_backup_info::v3::Request>,
) -> Result<get_backup_info::v3::Response> { ) -> Result<get_backup_info::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let algorithm = services() let algorithm = services
.key_backups .key_backups
.get_backup(sender_user, &body.version)? .get_backup(sender_user, &body.version)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
@@ -81,14 +82,12 @@ pub(crate) async fn get_backup_info_route(
Ok(get_backup_info::v3::Response { Ok(get_backup_info::v3::Response {
algorithm, algorithm,
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
version: body.version.clone(), version: body.version.clone(),
}) })
} }
@@ -100,11 +99,11 @@ pub(crate) async fn get_backup_info_route(
/// - Deletes both information about the backup, as well as all key data related /// - Deletes both information about the backup, as well as all key data related
/// to the backup /// to the backup
pub(crate) async fn delete_backup_version_route( pub(crate) async fn delete_backup_version_route(
body: Ruma<delete_backup_version::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_backup_version::v3::Request>,
) -> Result<delete_backup_version::v3::Response> { ) -> Result<delete_backup_version::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services
.key_backups .key_backups
.delete_backup(sender_user, &body.version)?; .delete_backup(sender_user, &body.version)?;
@@ -120,12 +119,12 @@ pub(crate) async fn delete_backup_version_route(
/// - Adds the keys to the backup /// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag /// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_route( pub(crate) async fn add_backup_keys_route(
body: Ruma<add_backup_keys::v3::Request>, State(services): State<crate::State>, body: Ruma<add_backup_keys::v3::Request>,
) -> Result<add_backup_keys::v3::Response> { ) -> Result<add_backup_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version) if Some(&body.version)
!= services() != services
.key_backups .key_backups
.get_latest_backup_version(sender_user)? .get_latest_backup_version(sender_user)?
.as_ref() .as_ref()
@@ -138,7 +137,7 @@ pub(crate) async fn add_backup_keys_route(
for (room_id, room) in &body.rooms { for (room_id, room) in &body.rooms {
for (session_id, key_data) in &room.sessions { for (session_id, key_data) in &room.sessions {
services() services
.key_backups .key_backups
.add_key(sender_user, &body.version, room_id, session_id, key_data)?; .add_key(sender_user, &body.version, room_id, session_id, key_data)?;
} }
@@ -146,14 +145,12 @@ pub(crate) async fn add_backup_keys_route(
Ok(add_backup_keys::v3::Response { Ok(add_backup_keys::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
@@ -166,12 +163,12 @@ pub(crate) async fn add_backup_keys_route(
/// - Adds the keys to the backup /// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag /// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_room_route( pub(crate) async fn add_backup_keys_for_room_route(
body: Ruma<add_backup_keys_for_room::v3::Request>, State(services): State<crate::State>, body: Ruma<add_backup_keys_for_room::v3::Request>,
) -> Result<add_backup_keys_for_room::v3::Response> { ) -> Result<add_backup_keys_for_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version) if Some(&body.version)
!= services() != services
.key_backups .key_backups
.get_latest_backup_version(sender_user)? .get_latest_backup_version(sender_user)?
.as_ref() .as_ref()
@@ -183,21 +180,19 @@ pub(crate) async fn add_backup_keys_for_room_route(
} }
for (session_id, key_data) in &body.sessions { for (session_id, key_data) in &body.sessions {
services() services
.key_backups .key_backups
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?; .add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
} }
Ok(add_backup_keys_for_room::v3::Response { Ok(add_backup_keys_for_room::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
@@ -210,12 +205,12 @@ pub(crate) async fn add_backup_keys_for_room_route(
/// - Adds the keys to the backup /// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag /// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_session_route( pub(crate) async fn add_backup_keys_for_session_route(
body: Ruma<add_backup_keys_for_session::v3::Request>, State(services): State<crate::State>, body: Ruma<add_backup_keys_for_session::v3::Request>,
) -> Result<add_backup_keys_for_session::v3::Response> { ) -> Result<add_backup_keys_for_session::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version) if Some(&body.version)
!= services() != services
.key_backups .key_backups
.get_latest_backup_version(sender_user)? .get_latest_backup_version(sender_user)?
.as_ref() .as_ref()
@@ -226,20 +221,18 @@ pub(crate) async fn add_backup_keys_for_session_route(
)); ));
} }
services() services
.key_backups .key_backups
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?; .add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
Ok(add_backup_keys_for_session::v3::Response { Ok(add_backup_keys_for_session::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
@@ -247,11 +240,11 @@ pub(crate) async fn add_backup_keys_for_session_route(
/// ///
/// Retrieves all keys from the backup. /// Retrieves all keys from the backup.
pub(crate) async fn get_backup_keys_route( pub(crate) async fn get_backup_keys_route(
body: Ruma<get_backup_keys::v3::Request>, State(services): State<crate::State>, body: Ruma<get_backup_keys::v3::Request>,
) -> Result<get_backup_keys::v3::Response> { ) -> Result<get_backup_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let rooms = services().key_backups.get_all(sender_user, &body.version)?; let rooms = services.key_backups.get_all(sender_user, &body.version)?;
Ok(get_backup_keys::v3::Response { Ok(get_backup_keys::v3::Response {
rooms, rooms,
@@ -262,11 +255,11 @@ pub(crate) async fn get_backup_keys_route(
/// ///
/// Retrieves all keys from the backup for a given room. /// Retrieves all keys from the backup for a given room.
pub(crate) async fn get_backup_keys_for_room_route( pub(crate) async fn get_backup_keys_for_room_route(
body: Ruma<get_backup_keys_for_room::v3::Request>, State(services): State<crate::State>, body: Ruma<get_backup_keys_for_room::v3::Request>,
) -> Result<get_backup_keys_for_room::v3::Response> { ) -> Result<get_backup_keys_for_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sessions = services() let sessions = services
.key_backups .key_backups
.get_room(sender_user, &body.version, &body.room_id)?; .get_room(sender_user, &body.version, &body.room_id)?;
@@ -279,11 +272,11 @@ pub(crate) async fn get_backup_keys_for_room_route(
/// ///
/// Retrieves a key from the backup. /// Retrieves a key from the backup.
pub(crate) async fn get_backup_keys_for_session_route( pub(crate) async fn get_backup_keys_for_session_route(
body: Ruma<get_backup_keys_for_session::v3::Request>, State(services): State<crate::State>, body: Ruma<get_backup_keys_for_session::v3::Request>,
) -> Result<get_backup_keys_for_session::v3::Response> { ) -> Result<get_backup_keys_for_session::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let key_data = services() let key_data = services
.key_backups .key_backups
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)? .get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?;
@@ -297,24 +290,22 @@ pub(crate) async fn get_backup_keys_for_session_route(
/// ///
/// Delete the keys from the backup. /// Delete the keys from the backup.
pub(crate) async fn delete_backup_keys_route( pub(crate) async fn delete_backup_keys_route(
body: Ruma<delete_backup_keys::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_backup_keys::v3::Request>,
) -> Result<delete_backup_keys::v3::Response> { ) -> Result<delete_backup_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services
.key_backups .key_backups
.delete_all_keys(sender_user, &body.version)?; .delete_all_keys(sender_user, &body.version)?;
Ok(delete_backup_keys::v3::Response { Ok(delete_backup_keys::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
@@ -322,24 +313,22 @@ pub(crate) async fn delete_backup_keys_route(
/// ///
/// Delete the keys from the backup for a given room. /// Delete the keys from the backup for a given room.
pub(crate) async fn delete_backup_keys_for_room_route( pub(crate) async fn delete_backup_keys_for_room_route(
body: Ruma<delete_backup_keys_for_room::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_room::v3::Request>,
) -> Result<delete_backup_keys_for_room::v3::Response> { ) -> Result<delete_backup_keys_for_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services
.key_backups .key_backups
.delete_room_keys(sender_user, &body.version, &body.room_id)?; .delete_room_keys(sender_user, &body.version, &body.room_id)?;
Ok(delete_backup_keys_for_room::v3::Response { Ok(delete_backup_keys_for_room::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
@@ -347,23 +336,21 @@ pub(crate) async fn delete_backup_keys_for_room_route(
/// ///
/// Delete a key from the backup. /// Delete a key from the backup.
pub(crate) async fn delete_backup_keys_for_session_route( pub(crate) async fn delete_backup_keys_for_session_route(
body: Ruma<delete_backup_keys_for_session::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_session::v3::Request>,
) -> Result<delete_backup_keys_for_session::v3::Response> { ) -> Result<delete_backup_keys_for_session::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services
.key_backups .key_backups
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?; .delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
Ok(delete_backup_keys_for_session::v3::Response { Ok(delete_backup_keys_for_session::v3::Response {
count: (UInt::try_from( count: (UInt::try_from(
services() services
.key_backups .key_backups
.count_keys(sender_user, &body.version)?, .count_keys(sender_user, &body.version)?,
) )
.expect("user backup keys count should not be that high")), .expect("user backup keys count should not be that high")),
etag: services() etag: services.key_backups.get_etag(sender_user, &body.version)?,
.key_backups
.get_etag(sender_user, &body.version)?,
}) })
} }
+6 -5
View File
@@ -1,29 +1,30 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State;
use ruma::api::client::discovery::get_capabilities::{ use ruma::api::client::discovery::get_capabilities::{
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability, self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
}; };
use crate::{services, Result, Ruma}; use crate::{Result, Ruma};
/// # `GET /_matrix/client/v3/capabilities` /// # `GET /_matrix/client/v3/capabilities`
/// ///
/// Get information on the supported feature set and other relevent capabilities /// Get information on the supported feature set and other relevent capabilities
/// of this server. /// of this server.
pub(crate) async fn get_capabilities_route( pub(crate) async fn get_capabilities_route(
_body: Ruma<get_capabilities::v3::Request>, State(services): State<crate::State>, _body: Ruma<get_capabilities::v3::Request>,
) -> Result<get_capabilities::v3::Response> { ) -> Result<get_capabilities::v3::Response> {
let mut available = BTreeMap::new(); let mut available = BTreeMap::new();
for room_version in &services().globals.unstable_room_versions { for room_version in &services.globals.unstable_room_versions {
available.insert(room_version.clone(), RoomVersionStability::Unstable); available.insert(room_version.clone(), RoomVersionStability::Unstable);
} }
for room_version in &services().globals.stable_room_versions { for room_version in &services.globals.stable_room_versions {
available.insert(room_version.clone(), RoomVersionStability::Stable); available.insert(room_version.clone(), RoomVersionStability::Stable);
} }
let mut capabilities = Capabilities::default(); let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability { capabilities.room_versions = RoomVersionsCapability {
default: services().globals.default_room_version(), default: services.globals.default_room_version(),
available, available,
}; };
+19 -10
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{ api::client::{
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data}, config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
@@ -10,15 +11,21 @@ use ruma::{
use serde::Deserialize; use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue}; use serde_json::{json, value::RawValue as RawJsonValue};
use crate::{services, Error, Result, Ruma}; use crate::{service::Services, Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}` /// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
/// ///
/// Sets some account data for the sender user. /// Sets some account data for the sender user.
pub(crate) async fn set_global_account_data_route( pub(crate) async fn set_global_account_data_route(
body: Ruma<set_global_account_data::v3::Request>, State(services): State<crate::State>, body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> { ) -> Result<set_global_account_data::v3::Response> {
set_account_data(None, &body.sender_user, &body.event_type.to_string(), body.data.json())?; set_account_data(
&services,
None,
&body.sender_user,
&body.event_type.to_string(),
body.data.json(),
)?;
Ok(set_global_account_data::v3::Response {}) Ok(set_global_account_data::v3::Response {})
} }
@@ -27,9 +34,10 @@ pub(crate) async fn set_global_account_data_route(
/// ///
/// Sets some room account data for the sender user. /// Sets some room account data for the sender user.
pub(crate) async fn set_room_account_data_route( pub(crate) async fn set_room_account_data_route(
body: Ruma<set_room_account_data::v3::Request>, State(services): State<crate::State>, body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> { ) -> Result<set_room_account_data::v3::Response> {
set_account_data( set_account_data(
&services,
Some(&body.room_id), Some(&body.room_id),
&body.sender_user, &body.sender_user,
&body.event_type.to_string(), &body.event_type.to_string(),
@@ -43,11 +51,11 @@ pub(crate) async fn set_room_account_data_route(
/// ///
/// Gets some account data for the sender user. /// Gets some account data for the sender user.
pub(crate) async fn get_global_account_data_route( pub(crate) async fn get_global_account_data_route(
body: Ruma<get_global_account_data::v3::Request>, State(services): State<crate::State>, body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> { ) -> Result<get_global_account_data::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services() let event: Box<RawJsonValue> = services
.account_data .account_data
.get(None, sender_user, body.event_type.to_string().into())? .get(None, sender_user, body.event_type.to_string().into())?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
@@ -65,11 +73,11 @@ pub(crate) async fn get_global_account_data_route(
/// ///
/// Gets some room account data for the sender user. /// Gets some room account data for the sender user.
pub(crate) async fn get_room_account_data_route( pub(crate) async fn get_room_account_data_route(
body: Ruma<get_room_account_data::v3::Request>, State(services): State<crate::State>, body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> { ) -> Result<get_room_account_data::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services() let event: Box<RawJsonValue> = services
.account_data .account_data
.get(Some(&body.room_id), sender_user, body.event_type.clone())? .get(Some(&body.room_id), sender_user, body.event_type.clone())?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
@@ -84,14 +92,15 @@ pub(crate) async fn get_room_account_data_route(
} }
fn set_account_data( fn set_account_data(
room_id: Option<&RoomId>, sender_user: &Option<OwnedUserId>, event_type: &str, data: &RawJsonValue, services: &Services, room_id: Option<&RoomId>, sender_user: &Option<OwnedUserId>, event_type: &str,
data: &RawJsonValue,
) -> Result<()> { ) -> Result<()> {
let sender_user = sender_user.as_ref().expect("user is authenticated"); let sender_user = sender_user.as_ref().expect("user is authenticated");
let data: serde_json::Value = let data: serde_json::Value =
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?; serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
services().account_data.update( services.account_data.update(
room_id, room_id,
sender_user, sender_user,
event_type.into(), event_type.into(),
+24 -19
View File
@@ -1,12 +1,13 @@
use std::collections::HashSet; use std::collections::HashSet;
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions}, api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
events::StateEventType, events::StateEventType,
}; };
use tracing::error; use tracing::error;
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/rooms/{roomId}/context` /// # `GET /_matrix/client/r0/rooms/{roomId}/context`
/// ///
@@ -14,26 +15,30 @@ use crate::{services, Error, Result, Ruma};
/// ///
/// - Only works if the user is joined (TODO: always allow, but only show events /// - Only works if the user is joined (TODO: always allow, but only show events
/// if the user was joined, depending on history_visibility) /// if the user was joined, depending on history_visibility)
pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> Result<get_context::v3::Response> { pub(crate) async fn get_context_route(
State(services): State<crate::State>, body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
// some clients, at least element, seem to require knowledge of redundant
// members for "inline" profiles on the timeline to work properly
let (lazy_load_enabled, lazy_load_send_redundant) = match &body.filter.lazy_load_options { let (lazy_load_enabled, lazy_load_send_redundant) = match &body.filter.lazy_load_options {
LazyLoadOptions::Enabled { LazyLoadOptions::Enabled {
include_redundant_members, include_redundant_members,
} => (true, *include_redundant_members), } => (true, *include_redundant_members),
LazyLoadOptions::Disabled => (false, false), LazyLoadOptions::Disabled => (false, cfg!(feature = "element_hacks")),
}; };
let mut lazy_loaded = HashSet::new(); let mut lazy_loaded = HashSet::new();
let base_token = services() let base_token = services
.rooms .rooms
.timeline .timeline
.get_pdu_count(&body.event_id)? .get_pdu_count(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?;
let base_event = services() let base_event = services
.rooms .rooms
.timeline .timeline
.get_pdu(&body.event_id)? .get_pdu(&body.event_id)?
@@ -41,7 +46,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
let room_id = base_event.room_id.clone(); let room_id = base_event.room_id.clone();
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &room_id, &body.event_id)? .user_can_see_event(sender_user, &room_id, &body.event_id)?
@@ -52,7 +57,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
)); ));
} }
if !services().rooms.lazy_loading.lazy_load_was_sent_before( if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
&room_id, &room_id,
@@ -67,14 +72,14 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
let base_event = base_event.to_room_event(); let base_event = base_event.to_room_event();
let events_before: Vec<_> = services() let events_before: Vec<_> = services
.rooms .rooms
.timeline .timeline
.pdus_until(sender_user, &room_id, base_token)? .pdus_until(sender_user, &room_id, base_token)?
.take(limit / 2) .take(limit / 2)
.filter_map(Result::ok) // Remove buggy events .filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| { .filter(|(_, pdu)| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id) .user_can_see_event(sender_user, &room_id, &pdu.event_id)
@@ -83,7 +88,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
.collect(); .collect();
for (_, event) in &events_before { for (_, event) in &events_before {
if !services().rooms.lazy_loading.lazy_load_was_sent_before( if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
&room_id, &room_id,
@@ -103,14 +108,14 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
.map(|(_, pdu)| pdu.to_room_event()) .map(|(_, pdu)| pdu.to_room_event())
.collect(); .collect();
let events_after: Vec<_> = services() let events_after: Vec<_> = services
.rooms .rooms
.timeline .timeline
.pdus_after(sender_user, &room_id, base_token)? .pdus_after(sender_user, &room_id, base_token)?
.take(limit / 2) .take(limit / 2)
.filter_map(Result::ok) // Remove buggy events .filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| { .filter(|(_, pdu)| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id) .user_can_see_event(sender_user, &room_id, &pdu.event_id)
@@ -119,7 +124,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
.collect(); .collect();
for (_, event) in &events_after { for (_, event) in &events_after {
if !services().rooms.lazy_loading.lazy_load_was_sent_before( if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
&room_id, &room_id,
@@ -130,7 +135,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
} }
} }
let shortstatehash = services() let shortstatehash = services
.rooms .rooms
.state_accessor .state_accessor
.pdu_shortstatehash( .pdu_shortstatehash(
@@ -139,7 +144,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
.map_or(&*body.event_id, |(_, e)| &*e.event_id), .map_or(&*body.event_id, |(_, e)| &*e.event_id),
)? )?
.map_or( .map_or(
services() services
.rooms .rooms
.state .state
.get_room_shortstatehash(&room_id)? .get_room_shortstatehash(&room_id)?
@@ -147,7 +152,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|hash| hash, |hash| hash,
); );
let state_ids = services() let state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(shortstatehash) .state_full_ids(shortstatehash)
@@ -165,20 +170,20 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
let mut state = Vec::with_capacity(state_ids.len()); let mut state = Vec::with_capacity(state_ids.len());
for (shortstatekey, id) in state_ids { for (shortstatekey, id) in state_ids {
let (event_type, state_key) = services() let (event_type, state_key) = services
.rooms .rooms
.short .short
.get_statekey_from_short(shortstatekey)?; .get_statekey_from_short(shortstatekey)?;
if event_type != StateEventType::RoomMember { if event_type != StateEventType::RoomMember {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
state.push(pdu.to_state_event()); state.push(pdu.to_state_event());
} else if !lazy_load_enabled || lazy_loaded.contains(&state_key) { } else if !lazy_load_enabled || lazy_loaded.contains(&state_key) {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
+25 -18
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::api::client::{ use ruma::api::client::{
device::{self, delete_device, delete_devices, get_device, get_devices, update_device}, device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
error::ErrorKind, error::ErrorKind,
@@ -5,15 +6,17 @@ use ruma::api::client::{
}; };
use super::SESSION_ID_LENGTH; use super::SESSION_ID_LENGTH;
use crate::{services, utils, Error, Result, Ruma}; use crate::{utils, Error, Result, Ruma};
/// # `GET /_matrix/client/r0/devices` /// # `GET /_matrix/client/r0/devices`
/// ///
/// Get metadata on all devices of the sender user. /// Get metadata on all devices of the sender user.
pub(crate) async fn get_devices_route(body: Ruma<get_devices::v3::Request>) -> Result<get_devices::v3::Response> { pub(crate) async fn get_devices_route(
State(services): State<crate::State>, body: Ruma<get_devices::v3::Request>,
) -> Result<get_devices::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let devices: Vec<device::Device> = services() let devices: Vec<device::Device> = services
.users .users
.all_devices_metadata(sender_user) .all_devices_metadata(sender_user)
.filter_map(Result::ok) // Filter out buggy devices .filter_map(Result::ok) // Filter out buggy devices
@@ -27,10 +30,12 @@ pub(crate) async fn get_devices_route(body: Ruma<get_devices::v3::Request>) -> R
/// # `GET /_matrix/client/r0/devices/{deviceId}` /// # `GET /_matrix/client/r0/devices/{deviceId}`
/// ///
/// Get metadata on a single device of the sender user. /// Get metadata on a single device of the sender user.
pub(crate) async fn get_device_route(body: Ruma<get_device::v3::Request>) -> Result<get_device::v3::Response> { pub(crate) async fn get_device_route(
State(services): State<crate::State>, body: Ruma<get_device::v3::Request>,
) -> Result<get_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device = services() let device = services
.users .users
.get_device_metadata(sender_user, &body.body.device_id)? .get_device_metadata(sender_user, &body.body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
@@ -43,17 +48,19 @@ pub(crate) async fn get_device_route(body: Ruma<get_device::v3::Request>) -> Res
/// # `PUT /_matrix/client/r0/devices/{deviceId}` /// # `PUT /_matrix/client/r0/devices/{deviceId}`
/// ///
/// Updates the metadata on a given device of the sender user. /// Updates the metadata on a given device of the sender user.
pub(crate) async fn update_device_route(body: Ruma<update_device::v3::Request>) -> Result<update_device::v3::Response> { pub(crate) async fn update_device_route(
State(services): State<crate::State>, body: Ruma<update_device::v3::Request>,
) -> Result<update_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device = services() let mut device = services
.users .users
.get_device_metadata(sender_user, &body.device_id)? .get_device_metadata(sender_user, &body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
device.display_name.clone_from(&body.display_name); device.display_name.clone_from(&body.display_name);
services() services
.users .users
.update_device_metadata(sender_user, &body.device_id, &device)?; .update_device_metadata(sender_user, &body.device_id, &device)?;
@@ -70,7 +77,9 @@ pub(crate) async fn update_device_route(body: Ruma<update_device::v3::Request>)
/// last seen ts) /// last seen ts)
/// - Forgets to-device events /// - Forgets to-device events
/// - Triggers device list updates /// - Triggers device list updates
pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>) -> Result<delete_device::v3::Response> { pub(crate) async fn delete_device_route(
State(services): State<crate::State>, body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -86,7 +95,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
}; };
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services() let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?; .try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked { if !worked {
@@ -95,7 +104,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services() services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
@@ -103,9 +112,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
} }
services() services.users.remove_device(sender_user, &body.device_id)?;
.users
.remove_device(sender_user, &body.device_id)?;
Ok(delete_device::v3::Response {}) Ok(delete_device::v3::Response {})
} }
@@ -123,7 +130,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
/// - Forgets to-device events /// - Forgets to-device events
/// - Triggers device list updates /// - Triggers device list updates
pub(crate) async fn delete_devices_route( pub(crate) async fn delete_devices_route(
body: Ruma<delete_devices::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::v3::Response> { ) -> Result<delete_devices::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -140,7 +147,7 @@ pub(crate) async fn delete_devices_route(
}; };
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services() let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?; .try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked { if !worked {
@@ -149,7 +156,7 @@ pub(crate) async fn delete_devices_route(
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services() services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
@@ -158,7 +165,7 @@ pub(crate) async fn delete_devices_route(
} }
for device_id in &body.devices { for device_id in &body.devices {
services().users.remove_device(sender_user, device_id)?; services.users.remove_device(sender_user, device_id)?;
} }
Ok(delete_devices::v3::Response {}) Ok(delete_devices::v3::Response {})
+41 -34
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Error, Result}; use conduit::{err, info, warn, Error, Result};
use ruma::{ use ruma::{
@@ -19,8 +20,9 @@ use ruma::{
}, },
uint, RoomId, ServerName, UInt, UserId, uint, RoomId, ServerName, UInt, UserId,
}; };
use service::Services;
use crate::{service::server_is_ours, services, Ruma}; use crate::Ruma;
/// # `POST /_matrix/client/v3/publicRooms` /// # `POST /_matrix/client/v3/publicRooms`
/// ///
@@ -29,10 +31,11 @@ use crate::{service::server_is_ours, services, Ruma};
/// - Rooms are ordered by the number of joined members /// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")] #[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_filtered_route( pub(crate) async fn get_public_rooms_filtered_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> { ) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(server) = &body.server { if let Some(server) = &body.server {
if services() if services
.globals .globals
.forbidden_remote_room_directory_server_names() .forbidden_remote_room_directory_server_names()
.contains(server) .contains(server)
@@ -45,6 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
} }
let response = get_public_rooms_filtered_helper( let response = get_public_rooms_filtered_helper(
&services,
body.server.as_deref(), body.server.as_deref(),
body.limit, body.limit,
body.since.as_deref(), body.since.as_deref(),
@@ -67,10 +71,11 @@ pub(crate) async fn get_public_rooms_filtered_route(
/// - Rooms are ordered by the number of joined members /// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")] #[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_route( pub(crate) async fn get_public_rooms_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> { ) -> Result<get_public_rooms::v3::Response> {
if let Some(server) = &body.server { if let Some(server) = &body.server {
if services() if services
.globals .globals
.forbidden_remote_room_directory_server_names() .forbidden_remote_room_directory_server_names()
.contains(server) .contains(server)
@@ -83,6 +88,7 @@ pub(crate) async fn get_public_rooms_route(
} }
let response = get_public_rooms_filtered_helper( let response = get_public_rooms_filtered_helper(
&services,
body.server.as_deref(), body.server.as_deref(),
body.limit, body.limit,
body.since.as_deref(), body.since.as_deref(),
@@ -108,16 +114,17 @@ pub(crate) async fn get_public_rooms_route(
/// Sets the visibility of a given room in the room directory. /// Sets the visibility of a given room in the room directory.
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")] #[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
pub(crate) async fn set_room_visibility_route( pub(crate) async fn set_room_visibility_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<set_room_visibility::v3::Request>,
) -> Result<set_room_visibility::v3::Response> { ) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services().rooms.metadata.exists(&body.room_id)? { if !services.rooms.metadata.exists(&body.room_id)? {
// Return 404 if the room doesn't exist // Return 404 if the room doesn't exist
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found")); return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
} }
if !user_can_publish_room(sender_user, &body.room_id)? { if !user_can_publish_room(&services, sender_user, &body.room_id)? {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"User is not allowed to publish this room", "User is not allowed to publish this room",
@@ -126,7 +133,7 @@ pub(crate) async fn set_room_visibility_route(
match &body.visibility { match &body.visibility {
room::Visibility::Public => { room::Visibility::Public => {
if services().globals.config.lockdown_public_room_directory && !services().users.is_admin(sender_user)? { if services.globals.config.lockdown_public_room_directory && !services.users.is_admin(sender_user)? {
info!( info!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \ "Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled", \"lockdown_public_room_directory\" is enabled",
@@ -139,10 +146,10 @@ pub(crate) async fn set_room_visibility_route(
)); ));
} }
services().rooms.directory.set_public(&body.room_id)?; services.rooms.directory.set_public(&body.room_id)?;
info!("{sender_user} made {0} public", body.room_id); info!("{sender_user} made {0} public", body.room_id);
}, },
room::Visibility::Private => services().rooms.directory.set_not_public(&body.room_id)?, room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id)?,
_ => { _ => {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
@@ -158,15 +165,15 @@ pub(crate) async fn set_room_visibility_route(
/// ///
/// Gets the visibility of a given room in the room directory. /// Gets the visibility of a given room in the room directory.
pub(crate) async fn get_room_visibility_route( pub(crate) async fn get_room_visibility_route(
body: Ruma<get_room_visibility::v3::Request>, State(services): State<crate::State>, body: Ruma<get_room_visibility::v3::Request>,
) -> Result<get_room_visibility::v3::Response> { ) -> Result<get_room_visibility::v3::Response> {
if !services().rooms.metadata.exists(&body.room_id)? { if !services.rooms.metadata.exists(&body.room_id)? {
// Return 404 if the room doesn't exist // Return 404 if the room doesn't exist
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found")); return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
} }
Ok(get_room_visibility::v3::Response { Ok(get_room_visibility::v3::Response {
visibility: if services().rooms.directory.is_public_room(&body.room_id)? { visibility: if services.rooms.directory.is_public_room(&body.room_id)? {
room::Visibility::Public room::Visibility::Public
} else { } else {
room::Visibility::Private room::Visibility::Private
@@ -175,10 +182,11 @@ pub(crate) async fn get_room_visibility_route(
} }
pub(crate) async fn get_public_rooms_filtered_helper( pub(crate) async fn get_public_rooms_filtered_helper(
server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter, _network: &RoomNetwork, services: &Services, server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter,
_network: &RoomNetwork,
) -> Result<get_public_rooms_filtered::v3::Response> { ) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(other_server) = server.filter(|server_name| !server_is_ours(server_name)) { if let Some(other_server) = server.filter(|server_name| !services.globals.server_is_ours(server_name)) {
let response = services() let response = services
.sending .sending
.send_federation_request( .send_federation_request(
other_server, other_server,
@@ -224,7 +232,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
} }
} }
let mut all_rooms: Vec<_> = services() let mut all_rooms: Vec<_> = services
.rooms .rooms
.directory .directory
.public_rooms() .public_rooms()
@@ -232,12 +240,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
let room_id = room_id?; let room_id = room_id?;
let chunk = PublicRoomsChunk { let chunk = PublicRoomsChunk {
canonical_alias: services() canonical_alias: services
.rooms .rooms
.state_accessor .state_accessor
.get_canonical_alias(&room_id)?, .get_canonical_alias(&room_id)?,
name: services().rooms.state_accessor.get_name(&room_id)?, name: services.rooms.state_accessor.get_name(&room_id)?,
num_joined_members: services() num_joined_members: services
.rooms .rooms
.state_cache .state_cache
.room_joined_count(&room_id)? .room_joined_count(&room_id)?
@@ -247,24 +255,24 @@ pub(crate) async fn get_public_rooms_filtered_helper(
}) })
.try_into() .try_into()
.expect("user count should not be that big"), .expect("user count should not be that big"),
topic: services() topic: services
.rooms .rooms
.state_accessor .state_accessor
.get_room_topic(&room_id) .get_room_topic(&room_id)
.unwrap_or(None), .unwrap_or(None),
world_readable: services().rooms.state_accessor.is_world_readable(&room_id)?, world_readable: services.rooms.state_accessor.is_world_readable(&room_id)?,
guest_can_join: services() guest_can_join: services
.rooms .rooms
.state_accessor .state_accessor
.guest_can_join(&room_id)?, .guest_can_join(&room_id)?,
avatar_url: services() avatar_url: services
.rooms .rooms
.state_accessor .state_accessor
.get_avatar(&room_id)? .get_avatar(&room_id)?
.into_option() .into_option()
.unwrap_or_default() .unwrap_or_default()
.url, .url,
join_rule: services() join_rule: services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")? .room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
@@ -282,7 +290,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
.transpose()? .transpose()?
.flatten() .flatten()
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?, .ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
room_type: services() room_type: services
.rooms .rooms
.state_accessor .state_accessor
.get_room_type(&room_id)?, .get_room_type(&room_id)?,
@@ -361,12 +369,11 @@ pub(crate) async fn get_public_rooms_filtered_helper(
/// Check whether the user can publish to the room directory via power levels of /// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator /// room history visibility event or room creator
fn user_can_publish_room(user_id: &UserId, room_id: &RoomId) -> Result<bool> { fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) = if let Some(event) = services
services() .rooms
.rooms .state_accessor
.state_accessor .room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
{ {
serde_json::from_str(event.content.get()) serde_json::from_str(event.content.get())
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels")) .map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
@@ -374,7 +381,7 @@ fn user_can_publish_room(user_id: &UserId, room_id: &RoomId) -> Result<bool> {
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility) RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
}) })
} else if let Some(event) = } else if let Some(event) =
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")? .room_state_get(room_id, &StateEventType::RoomCreate, "")?
+10 -5
View File
@@ -1,18 +1,21 @@
use axum::extract::State;
use ruma::api::client::{ use ruma::api::client::{
error::ErrorKind, error::ErrorKind,
filter::{create_filter, get_filter}, filter::{create_filter, get_filter},
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}` /// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
/// ///
/// Loads a filter that was previously created. /// Loads a filter that was previously created.
/// ///
/// - A user can only access their own filters /// - A user can only access their own filters
pub(crate) async fn get_filter_route(body: Ruma<get_filter::v3::Request>) -> Result<get_filter::v3::Response> { pub(crate) async fn get_filter_route(
State(services): State<crate::State>, body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let Some(filter) = services().users.get_filter(sender_user, &body.filter_id)? else { let Some(filter) = services.users.get_filter(sender_user, &body.filter_id)? else {
return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found."));
}; };
@@ -22,9 +25,11 @@ pub(crate) async fn get_filter_route(body: Ruma<get_filter::v3::Request>) -> Res
/// # `PUT /_matrix/client/r0/user/{userId}/filter` /// # `PUT /_matrix/client/r0/user/{userId}/filter`
/// ///
/// Creates a new filter to be used by other endpoints. /// Creates a new filter to be used by other endpoints.
pub(crate) async fn create_filter_route(body: Ruma<create_filter::v3::Request>) -> Result<create_filter::v3::Response> { pub(crate) async fn create_filter_route(
State(services): State<crate::State>, body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(create_filter::v3::Response::new( Ok(create_filter::v3::Response::new(
services().users.create_filter(sender_user, &body.filter)?, services.users.create_filter(sender_user, &body.filter)?,
)) ))
} }
+50 -44
View File
@@ -3,7 +3,8 @@ use std::{
time::Instant, time::Instant,
}; };
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Error, Result}; use axum::extract::State;
use conduit::{debug, utils, utils::math::continue_exponential_backoff_secs, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt}; use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{ use ruma::{
api::{ api::{
@@ -18,11 +19,9 @@ use ruma::{
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId, DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
}; };
use serde_json::json; use serde_json::json;
use service::user_is_local;
use tracing::debug;
use super::SESSION_ID_LENGTH; use super::SESSION_ID_LENGTH;
use crate::{services, Ruma}; use crate::{service::Services, Ruma};
/// # `POST /_matrix/client/r0/keys/upload` /// # `POST /_matrix/client/r0/keys/upload`
/// ///
@@ -31,12 +30,14 @@ use crate::{services, Ruma};
/// - Adds one time keys /// - Adds one time keys
/// - If there are no device keys yet: Adds device keys (TODO: merge with /// - If there are no device keys yet: Adds device keys (TODO: merge with
/// existing keys?) /// existing keys?)
pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> Result<upload_keys::v3::Response> { pub(crate) async fn upload_keys_route(
State(services): State<crate::State>, body: Ruma<upload_keys::v3::Request>,
) -> Result<upload_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
for (key_key, key_value) in &body.one_time_keys { for (key_key, key_value) in &body.one_time_keys {
services() services
.users .users
.add_one_time_key(sender_user, sender_device, key_key, key_value)?; .add_one_time_key(sender_user, sender_device, key_key, key_value)?;
} }
@@ -44,19 +45,19 @@ pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> R
if let Some(device_keys) = &body.device_keys { if let Some(device_keys) = &body.device_keys {
// TODO: merge this and the existing event? // TODO: merge this and the existing event?
// This check is needed to assure that signatures are kept // This check is needed to assure that signatures are kept
if services() if services
.users .users
.get_device_keys(sender_user, sender_device)? .get_device_keys(sender_user, sender_device)?
.is_none() .is_none()
{ {
services() services
.users .users
.add_device_keys(sender_user, sender_device, device_keys)?; .add_device_keys(sender_user, sender_device, device_keys)?;
} }
} }
Ok(upload_keys::v3::Response { Ok(upload_keys::v3::Response {
one_time_key_counts: services() one_time_key_counts: services
.users .users
.count_one_time_keys(sender_user, sender_device)?, .count_one_time_keys(sender_user, sender_device)?,
}) })
@@ -70,10 +71,13 @@ pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> R
/// - Gets master keys, self-signing keys, user signing keys and device keys. /// - Gets master keys, self-signing keys, user signing keys and device keys.
/// - The master and self-signing keys contain signatures that the user is /// - The master and self-signing keys contain signatures that the user is
/// allowed to see /// allowed to see
pub(crate) async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<get_keys::v3::Response> { pub(crate) async fn get_keys_route(
State(services): State<crate::State>, body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
get_keys_helper( get_keys_helper(
&services,
Some(sender_user), Some(sender_user),
&body.device_keys, &body.device_keys,
|u| u == sender_user, |u| u == sender_user,
@@ -85,8 +89,10 @@ pub(crate) async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<
/// # `POST /_matrix/client/r0/keys/claim` /// # `POST /_matrix/client/r0/keys/claim`
/// ///
/// Claims one-time keys /// Claims one-time keys
pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Result<claim_keys::v3::Response> { pub(crate) async fn claim_keys_route(
claim_keys_helper(&body.one_time_keys).await State(services): State<crate::State>, body: Ruma<claim_keys::v3::Request>,
) -> Result<claim_keys::v3::Response> {
claim_keys_helper(&services, &body.one_time_keys).await
} }
/// # `POST /_matrix/client/r0/keys/device_signing/upload` /// # `POST /_matrix/client/r0/keys/device_signing/upload`
@@ -95,7 +101,7 @@ pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Res
/// ///
/// - Requires UIAA to verify password /// - Requires UIAA to verify password
pub(crate) async fn upload_signing_keys_route( pub(crate) async fn upload_signing_keys_route(
body: Ruma<upload_signing_keys::v3::Request>, State(services): State<crate::State>, body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> { ) -> Result<upload_signing_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -112,7 +118,7 @@ pub(crate) async fn upload_signing_keys_route(
}; };
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services() let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?; .try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked { if !worked {
@@ -121,7 +127,7 @@ pub(crate) async fn upload_signing_keys_route(
// Success! // Success!
} else if let Some(json) = body.json_body { } else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services() services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?; .create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
@@ -130,7 +136,7 @@ pub(crate) async fn upload_signing_keys_route(
} }
if let Some(master_key) = &body.master_key { if let Some(master_key) = &body.master_key {
services().users.add_cross_signing_keys( services.users.add_cross_signing_keys(
sender_user, sender_user,
master_key, master_key,
&body.self_signing_key, &body.self_signing_key,
@@ -146,7 +152,7 @@ pub(crate) async fn upload_signing_keys_route(
/// ///
/// Uploads end-to-end key signatures from the sender user. /// Uploads end-to-end key signatures from the sender user.
pub(crate) async fn upload_signatures_route( pub(crate) async fn upload_signatures_route(
body: Ruma<upload_signatures::v3::Request>, State(services): State<crate::State>, body: Ruma<upload_signatures::v3::Request>,
) -> Result<upload_signatures::v3::Response> { ) -> Result<upload_signatures::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -173,7 +179,7 @@ pub(crate) async fn upload_signatures_route(
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))? .ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
.to_owned(), .to_owned(),
); );
services() services
.users .users
.sign_key(user_id, key_id, signature, sender_user)?; .sign_key(user_id, key_id, signature, sender_user)?;
} }
@@ -192,14 +198,14 @@ pub(crate) async fn upload_signatures_route(
/// ///
/// - TODO: left users /// - TODO: left users
pub(crate) async fn get_key_changes_route( pub(crate) async fn get_key_changes_route(
body: Ruma<get_key_changes::v3::Request>, State(services): State<crate::State>, body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> { ) -> Result<get_key_changes::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device_list_updates = HashSet::new(); let mut device_list_updates = HashSet::new();
device_list_updates.extend( device_list_updates.extend(
services() services
.users .users
.keys_changed( .keys_changed(
sender_user.as_str(), sender_user.as_str(),
@@ -215,14 +221,14 @@ pub(crate) async fn get_key_changes_route(
.filter_map(Result::ok), .filter_map(Result::ok),
); );
for room_id in services() for room_id in services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(sender_user)
.filter_map(Result::ok) .filter_map(Result::ok)
{ {
device_list_updates.extend( device_list_updates.extend(
services() services
.users .users
.keys_changed( .keys_changed(
room_id.as_ref(), room_id.as_ref(),
@@ -245,8 +251,8 @@ pub(crate) async fn get_key_changes_route(
} }
pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>( pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>, allowed_signatures: F, services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
include_display_names: bool, allowed_signatures: F, include_display_names: bool,
) -> Result<get_keys::v3::Response> { ) -> Result<get_keys::v3::Response> {
let mut master_keys = BTreeMap::new(); let mut master_keys = BTreeMap::new();
let mut self_signing_keys = BTreeMap::new(); let mut self_signing_keys = BTreeMap::new();
@@ -258,7 +264,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
for (user_id, device_ids) in device_keys_input { for (user_id, device_ids) in device_keys_input {
let user_id: &UserId = user_id; let user_id: &UserId = user_id;
if !user_is_local(user_id) { if !services.globals.user_is_local(user_id) {
get_over_federation get_over_federation
.entry(user_id.server_name()) .entry(user_id.server_name())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
@@ -268,10 +274,10 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
if device_ids.is_empty() { if device_ids.is_empty() {
let mut container = BTreeMap::new(); let mut container = BTreeMap::new();
for device_id in services().users.all_device_ids(user_id) { for device_id in services.users.all_device_ids(user_id) {
let device_id = device_id?; let device_id = device_id?;
if let Some(mut keys) = services().users.get_device_keys(user_id, &device_id)? { if let Some(mut keys) = services.users.get_device_keys(user_id, &device_id)? {
let metadata = services() let metadata = services
.users .users
.get_device_metadata(user_id, &device_id)? .get_device_metadata(user_id, &device_id)?
.ok_or_else(|| Error::bad_database("all_device_keys contained nonexistent device."))?; .ok_or_else(|| Error::bad_database("all_device_keys contained nonexistent device."))?;
@@ -286,8 +292,8 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
} else { } else {
for device_id in device_ids { for device_id in device_ids {
let mut container = BTreeMap::new(); let mut container = BTreeMap::new();
if let Some(mut keys) = services().users.get_device_keys(user_id, device_id)? { if let Some(mut keys) = services.users.get_device_keys(user_id, device_id)? {
let metadata = services() let metadata = services
.users .users
.get_device_metadata(user_id, device_id)? .get_device_metadata(user_id, device_id)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
@@ -303,21 +309,21 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
} }
} }
if let Some(master_key) = services() if let Some(master_key) = services
.users .users
.get_master_key(sender_user, user_id, &allowed_signatures)? .get_master_key(sender_user, user_id, &allowed_signatures)?
{ {
master_keys.insert(user_id.to_owned(), master_key); master_keys.insert(user_id.to_owned(), master_key);
} }
if let Some(self_signing_key) = if let Some(self_signing_key) =
services() services
.users .users
.get_self_signing_key(sender_user, user_id, &allowed_signatures)? .get_self_signing_key(sender_user, user_id, &allowed_signatures)?
{ {
self_signing_keys.insert(user_id.to_owned(), self_signing_key); self_signing_keys.insert(user_id.to_owned(), self_signing_key);
} }
if Some(user_id) == sender_user { if Some(user_id) == sender_user {
if let Some(user_signing_key) = services().users.get_user_signing_key(user_id)? { if let Some(user_signing_key) = services.users.get_user_signing_key(user_id)? {
user_signing_keys.insert(user_id.to_owned(), user_signing_key); user_signing_keys.insert(user_id.to_owned(), user_signing_key);
} }
} }
@@ -326,7 +332,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let mut failures = BTreeMap::new(); let mut failures = BTreeMap::new();
let back_off = |id| async { let back_off = |id| async {
match services() match services
.globals .globals
.bad_query_ratelimiter .bad_query_ratelimiter
.write() .write()
@@ -345,7 +351,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let mut futures: FuturesUnordered<_> = get_over_federation let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter() .into_iter()
.map(|(server, vec)| async move { .map(|(server, vec)| async move {
if let Some((time, tries)) = services() if let Some((time, tries)) = services
.globals .globals
.bad_query_ratelimiter .bad_query_ratelimiter
.read() .read()
@@ -369,7 +375,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let request = federation::keys::get_keys::v1::Request { let request = federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed, device_keys: device_keys_input_fed,
}; };
let response = services() let response = services
.sending .sending
.send_federation_request(server, request) .send_federation_request(server, request)
.await; .await;
@@ -381,19 +387,19 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
while let Some((server, response)) = futures.next().await { while let Some((server, response)) = futures.next().await {
if let Ok(Ok(response)) = response { if let Ok(Ok(response)) = response {
for (user, masterkey) in response.master_keys { for (user, masterkey) in response.master_keys {
let (master_key_id, mut master_key) = services().users.parse_master_key(&user, &masterkey)?; let (master_key_id, mut master_key) = services.users.parse_master_key(&user, &masterkey)?;
if let Some(our_master_key) = if let Some(our_master_key) =
services() services
.users .users
.get_key(&master_key_id, sender_user, &user, &allowed_signatures)? .get_key(&master_key_id, sender_user, &user, &allowed_signatures)?
{ {
let (_, our_master_key) = services().users.parse_master_key(&user, &our_master_key)?; let (_, our_master_key) = services.users.parse_master_key(&user, &our_master_key)?;
master_key.signatures.extend(our_master_key.signatures); master_key.signatures.extend(our_master_key.signatures);
} }
let json = serde_json::to_value(master_key).expect("to_value always works"); let json = serde_json::to_value(master_key).expect("to_value always works");
let raw = serde_json::from_value(json).expect("Raw::from_value always works"); let raw = serde_json::from_value(json).expect("Raw::from_value always works");
services().users.add_cross_signing_keys( services.users.add_cross_signing_keys(
&user, &raw, &None, &None, &user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key request resulting in an false, /* Dont notify. A notification would trigger another key request resulting in an
* endless loop */ * endless loop */
@@ -444,14 +450,14 @@ fn add_unsigned_device_display_name(
} }
pub(crate) async fn claim_keys_helper( pub(crate) async fn claim_keys_helper(
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, DeviceKeyAlgorithm>>, services: &Services, one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, DeviceKeyAlgorithm>>,
) -> Result<claim_keys::v3::Response> { ) -> Result<claim_keys::v3::Response> {
let mut one_time_keys = BTreeMap::new(); let mut one_time_keys = BTreeMap::new();
let mut get_over_federation = BTreeMap::new(); let mut get_over_federation = BTreeMap::new();
for (user_id, map) in one_time_keys_input { for (user_id, map) in one_time_keys_input {
if !user_is_local(user_id) { if !services.globals.user_is_local(user_id) {
get_over_federation get_over_federation
.entry(user_id.server_name()) .entry(user_id.server_name())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
@@ -460,7 +466,7 @@ pub(crate) async fn claim_keys_helper(
let mut container = BTreeMap::new(); let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map { for (device_id, key_algorithm) in map {
if let Some(one_time_keys) = services() if let Some(one_time_keys) = services
.users .users
.take_one_time_key(user_id, device_id, key_algorithm)? .take_one_time_key(user_id, device_id, key_algorithm)?
{ {
@@ -483,7 +489,7 @@ pub(crate) async fn claim_keys_helper(
} }
( (
server, server,
services() services
.sending .sending
.send_federation_request( .send_federation_request(
server, server,
+98 -112
View File
@@ -1,34 +1,35 @@
#![allow(deprecated)] #![allow(deprecated)]
use std::{io::Cursor, sync::Arc, time::Duration}; use std::{io::Cursor, time::Duration};
use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{debug, error, utils::math::ruma_from_usize, warn}; use conduit::{
debug, debug_info, debug_warn, err, info,
utils::{
self,
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
math::ruma_from_usize,
},
warn, Err, Error, Result,
};
use image::io::Reader as ImgReader; use image::io::Reader as ImgReader;
use ipaddress::IPAddress; use ipaddress::IPAddress;
use reqwest::Url; use reqwest::Url;
use ruma::api::client::{ use ruma::api::client::{
error::{ErrorKind, RetryAfter}, error::ErrorKind,
media::{ media::{
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config, create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview, get_media_preview,
}, },
}; };
use service::{
media::{FileMeta, UrlPreviewData},
Services,
};
use webpage::HTML; use webpage::HTML;
use crate::{ use crate::{Ruma, RumaResponse};
debug_warn,
service::{
media::{FileMeta, UrlPreviewData},
server_is_ours,
},
services,
utils::{
self,
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
},
Error, Result, Ruma, RumaResponse,
};
/// generated MXC ID (`media-id`) length /// generated MXC ID (`media-id`) length
const MXC_LENGTH: usize = 32; const MXC_LENGTH: usize = 32;
@@ -42,10 +43,10 @@ const CORP_CROSS_ORIGIN: &str = "cross-origin";
/// ///
/// Returns max upload size. /// Returns max upload size.
pub(crate) async fn get_media_config_route( pub(crate) async fn get_media_config_route(
_body: Ruma<get_media_config::v3::Request>, State(services): State<crate::State>, _body: Ruma<get_media_config::v3::Request>,
) -> Result<get_media_config::v3::Response> { ) -> Result<get_media_config::v3::Response> {
Ok(get_media_config::v3::Response { Ok(get_media_config::v3::Response {
upload_size: ruma_from_usize(services().globals.config.max_request_size), upload_size: ruma_from_usize(services.globals.config.max_request_size),
}) })
} }
@@ -57,9 +58,11 @@ pub(crate) async fn get_media_config_route(
/// ///
/// Returns max upload size. /// Returns max upload size.
pub(crate) async fn get_media_config_v1_route( pub(crate) async fn get_media_config_v1_route(
body: Ruma<get_media_config::v3::Request>, State(services): State<crate::State>, body: Ruma<get_media_config::v3::Request>,
) -> Result<RumaResponse<get_media_config::v3::Response>> { ) -> Result<RumaResponse<get_media_config::v3::Response>> {
get_media_config_route(body).await.map(RumaResponse) get_media_config_route(State(services), body)
.await
.map(RumaResponse)
} }
/// # `GET /_matrix/media/v3/preview_url` /// # `GET /_matrix/media/v3/preview_url`
@@ -67,41 +70,29 @@ pub(crate) async fn get_media_config_v1_route(
/// Returns URL preview. /// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")] #[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_route( pub(crate) async fn get_media_preview_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> { ) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url; let url = &body.url;
if !url_preview_allowed(url) { if !url_preview_allowed(&services, url) {
warn!(%sender_user, "URL is not allowed to be previewed: {url}"); debug_info!(%sender_user, "URL is not allowed to be previewed: {url}");
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed")); return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed"));
} }
match get_url_preview(url).await { match get_url_preview(&services, url).await {
Ok(preview) => { Ok(preview) => {
let res = serde_json::value::to_raw_value(&preview).map_err(|e| { let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}"); warn!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
Error::BadRequest( err!(Request(Unknown("Failed to generate a URL preview")))
ErrorKind::LimitExceeded {
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
},
"Failed to generate a URL preview, try again later.",
)
})?; })?;
Ok(get_media_preview::v3::Response::from_raw_value(res)) Ok(get_media_preview::v3::Response::from_raw_value(res))
}, },
Err(e) => { Err(e) => {
warn!(%sender_user, "Failed to generate a URL preview: {e}"); info!(%sender_user, "Failed to generate a URL preview: {e}");
Err!(Request(Unknown("Failed to generate a URL preview")))
// there doesn't seem to be an agreed-upon error code in the spec.
// the only response codes in the preview_url spec page are 200 and 429.
Err(Error::BadRequest(
ErrorKind::LimitExceeded {
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
},
"Failed to generate a URL preview, try again later.",
))
}, },
} }
} }
@@ -115,9 +106,10 @@ pub(crate) async fn get_media_preview_route(
/// Returns URL preview. /// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")] #[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_v1_route( pub(crate) async fn get_media_preview_v1_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v3::Request>,
) -> Result<RumaResponse<get_media_preview::v3::Response>> { ) -> Result<RumaResponse<get_media_preview::v3::Response>> {
get_media_preview_route(InsecureClientIp(client), body) get_media_preview_route(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@@ -130,17 +122,14 @@ pub(crate) async fn get_media_preview_v1_route(
/// - Media will be saved in the media/ directory /// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")] #[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_route( pub(crate) async fn create_content_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> { ) -> Result<create_content::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!( let mxc = format!("mxc://{}/{}", services.globals.server_name(), utils::random_string(MXC_LENGTH));
"mxc://{}/{}",
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
services() services
.media .media
.create( .create(
Some(sender_user.clone()), Some(sender_user.clone()),
@@ -178,9 +167,10 @@ pub(crate) async fn create_content_route(
/// - Media will be saved in the media/ directory /// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")] #[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_v1_route( pub(crate) async fn create_content_v1_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> { ) -> Result<RumaResponse<create_content::v3::Response>> {
create_content_route(InsecureClientIp(client), body) create_content_route(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@@ -195,7 +185,8 @@ pub(crate) async fn create_content_v1_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_route( pub(crate) async fn get_content_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v3::Request>,
) -> Result<get_content::v3::Response> { ) -> Result<get_content::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -203,7 +194,7 @@ pub(crate) async fn get_content_route(
content, content,
content_type, content_type,
content_disposition, content_disposition,
}) = services().media.get(&mxc).await? }) = services.media.get(&mxc).await?
{ {
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None)); let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
let file = content.expect("content"); let file = content.expect("content");
@@ -215,8 +206,9 @@ pub(crate) async fn get_content_route(
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()), cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
}) })
} else if !server_is_ours(&body.server_name) && body.allow_remote { } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
let response = get_remote_content( let response = get_remote_content(
&services,
&mxc, &mxc,
&body.server_name, &body.server_name,
body.media_id.clone(), body.media_id.clone(),
@@ -261,9 +253,10 @@ pub(crate) async fn get_content_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_v1_route( pub(crate) async fn get_content_v1_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v3::Request>,
) -> Result<RumaResponse<get_content::v3::Response>> { ) -> Result<RumaResponse<get_content::v3::Response>> {
get_content_route(InsecureClientIp(client), body) get_content_route(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@@ -278,7 +271,8 @@ pub(crate) async fn get_content_v1_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_as_filename_route( pub(crate) async fn get_content_as_filename_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<get_content_as_filename::v3::Response> { ) -> Result<get_content_as_filename::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -286,7 +280,7 @@ pub(crate) async fn get_content_as_filename_route(
content, content,
content_type, content_type,
content_disposition, content_disposition,
}) = services().media.get(&mxc).await? }) = services.media.get(&mxc).await?
{ {
let content_disposition = Some(make_content_disposition( let content_disposition = Some(make_content_disposition(
&content_type, &content_type,
@@ -302,8 +296,9 @@ pub(crate) async fn get_content_as_filename_route(
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()), cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
}) })
} else if !server_is_ours(&body.server_name) && body.allow_remote { } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
match get_remote_content( match get_remote_content(
&services,
&mxc, &mxc,
&body.server_name, &body.server_name,
body.media_id.clone(), body.media_id.clone(),
@@ -351,9 +346,10 @@ pub(crate) async fn get_content_as_filename_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_as_filename_v1_route( pub(crate) async fn get_content_as_filename_v1_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> { ) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
get_content_as_filename_route(InsecureClientIp(client), body) get_content_as_filename_route(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@@ -368,7 +364,8 @@ pub(crate) async fn get_content_as_filename_v1_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
pub(crate) async fn get_content_thumbnail_route( pub(crate) async fn get_content_thumbnail_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<get_content_thumbnail::v3::Response> { ) -> Result<get_content_thumbnail::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -376,7 +373,7 @@ pub(crate) async fn get_content_thumbnail_route(
content, content,
content_type, content_type,
content_disposition, content_disposition,
}) = services() }) = services
.media .media
.get_thumbnail( .get_thumbnail(
&mxc, &mxc,
@@ -399,8 +396,8 @@ pub(crate) async fn get_content_thumbnail_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
content_disposition, content_disposition,
}) })
} else if !server_is_ours(&body.server_name) && body.allow_remote { } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
if services() if services
.globals .globals
.prevent_media_downloads_from() .prevent_media_downloads_from()
.contains(&body.server_name) .contains(&body.server_name)
@@ -411,7 +408,7 @@ pub(crate) async fn get_content_thumbnail_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
} }
match services() match services
.sending .sending
.send_federation_request( .send_federation_request(
&body.server_name, &body.server_name,
@@ -430,7 +427,7 @@ pub(crate) async fn get_content_thumbnail_route(
.await .await
{ {
Ok(get_thumbnail_response) => { Ok(get_thumbnail_response) => {
services() services
.media .media
.upload_thumbnail( .upload_thumbnail(
None, None,
@@ -481,17 +478,19 @@ pub(crate) async fn get_content_thumbnail_route(
/// seconds /// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")] #[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
pub(crate) async fn get_content_thumbnail_v1_route( pub(crate) async fn get_content_thumbnail_v1_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> { ) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
get_content_thumbnail_route(InsecureClientIp(client), body) get_content_thumbnail_route(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
async fn get_remote_content( async fn get_remote_content(
mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool, timeout_ms: Duration, services: &Services, mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool,
timeout_ms: Duration,
) -> Result<get_content::v3::Response, Error> { ) -> Result<get_content::v3::Response, Error> {
if services() if services
.globals .globals
.prevent_media_downloads_from() .prevent_media_downloads_from()
.contains(&server_name.to_owned()) .contains(&server_name.to_owned())
@@ -502,7 +501,7 @@ async fn get_remote_content(
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
} }
let content_response = services() let content_response = services
.sending .sending
.send_federation_request( .send_federation_request(
server_name, server_name,
@@ -522,7 +521,7 @@ async fn get_remote_content(
None, None,
)); ));
services() services
.media .media
.create( .create(
None, None,
@@ -542,15 +541,11 @@ async fn get_remote_content(
}) })
} }
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> { async fn download_image(services: &Services, client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let image = client.get(url).send().await?.bytes().await?; let image = client.get(url).send().await?.bytes().await?;
let mxc = format!( let mxc = format!("mxc://{}/{}", services.globals.server_name(), utils::random_string(MXC_LENGTH));
"mxc://{}/{}",
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
services() services
.media .media
.create(None, &mxc, None, None, &image) .create(None, &mxc, None, None, &image)
.await?; .await?;
@@ -572,18 +567,18 @@ async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPrevie
}) })
} }
async fn download_html(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> { async fn download_html(services: &Services, client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let mut response = client.get(url).send().await?; let mut response = client.get(url).send().await?;
let mut bytes: Vec<u8> = Vec::new(); let mut bytes: Vec<u8> = Vec::new();
while let Some(chunk) = response.chunk().await? { while let Some(chunk) = response.chunk().await? {
bytes.extend_from_slice(&chunk); bytes.extend_from_slice(&chunk);
if bytes.len() > services().globals.url_preview_max_spider_size() { if bytes.len() > services.globals.url_preview_max_spider_size() {
debug!( debug!(
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \ "Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \
response body and assuming our necessary data is in this range.", response body and assuming our necessary data is in this range.",
url, url,
services().globals.url_preview_max_spider_size() services.globals.url_preview_max_spider_size()
); );
break; break;
} }
@@ -595,7 +590,7 @@ async fn download_html(client: &reqwest::Client, url: &str) -> Result<UrlPreview
let mut data = match html.opengraph.images.first() { let mut data = match html.opengraph.images.first() {
None => UrlPreviewData::default(), None => UrlPreviewData::default(),
Some(obj) => download_image(client, &obj.url).await?, Some(obj) => download_image(services, client, &obj.url).await?,
}; };
let props = html.opengraph.properties; let props = html.opengraph.properties;
@@ -607,19 +602,19 @@ async fn download_html(client: &reqwest::Client, url: &str) -> Result<UrlPreview
Ok(data) Ok(data)
} }
async fn request_url_preview(url: &str) -> Result<UrlPreviewData> { async fn request_url_preview(services: &Services, url: &str) -> Result<UrlPreviewData> {
if let Ok(ip) = IPAddress::parse(url) { if let Ok(ip) = IPAddress::parse(url) {
if !services().globals.valid_cidr_range(&ip) { if !services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden")); return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
} }
} }
let client = &services().globals.client.url_preview; let client = &services.client.url_preview;
let response = client.head(url).send().await?; let response = client.head(url).send().await?;
if let Some(remote_addr) = response.remote_addr() { if let Some(remote_addr) = response.remote_addr() {
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) { if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !services().globals.valid_cidr_range(&ip) { if !services.globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden")); return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
} }
} }
@@ -633,40 +628,31 @@ async fn request_url_preview(url: &str) -> Result<UrlPreviewData> {
return Err(Error::BadRequest(ErrorKind::Unknown, "Unknown Content-Type")); return Err(Error::BadRequest(ErrorKind::Unknown, "Unknown Content-Type"));
}; };
let data = match content_type { let data = match content_type {
html if html.starts_with("text/html") => download_html(client, url).await?, html if html.starts_with("text/html") => download_html(services, client, url).await?,
img if img.starts_with("image/") => download_image(client, url).await?, img if img.starts_with("image/") => download_image(services, client, url).await?,
_ => return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported Content-Type")), _ => return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported Content-Type")),
}; };
services().media.set_url_preview(url, &data).await?; services.media.set_url_preview(url, &data).await?;
Ok(data) Ok(data)
} }
async fn get_url_preview(url: &str) -> Result<UrlPreviewData> { async fn get_url_preview(services: &Services, url: &str) -> Result<UrlPreviewData> {
if let Some(preview) = services().media.get_url_preview(url).await { if let Some(preview) = services.media.get_url_preview(url).await {
return Ok(preview); return Ok(preview);
} }
// ensure that only one request is made per URL // ensure that only one request is made per URL
let mutex_request = Arc::clone( let _request_lock = services.media.url_preview_mutex.lock(url).await;
services()
.media
.url_preview_mutex
.write()
.await
.entry(url.to_owned())
.or_default(),
);
let _request_lock = mutex_request.lock().await;
match services().media.get_url_preview(url).await { match services.media.get_url_preview(url).await {
Some(preview) => Ok(preview), Some(preview) => Ok(preview),
None => request_url_preview(url).await, None => request_url_preview(services, url).await,
} }
} }
fn url_preview_allowed(url_str: &str) -> bool { fn url_preview_allowed(services: &Services, url_str: &str) -> bool {
let url: Url = match Url::parse(url_str) { let url: Url = match Url::parse(url_str) {
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {
@@ -691,10 +677,10 @@ fn url_preview_allowed(url_str: &str) -> bool {
Some(h) => h.to_owned(), Some(h) => h.to_owned(),
}; };
let allowlist_domain_contains = services().globals.url_preview_domain_contains_allowlist(); let allowlist_domain_contains = services.globals.url_preview_domain_contains_allowlist();
let allowlist_domain_explicit = services().globals.url_preview_domain_explicit_allowlist(); let allowlist_domain_explicit = services.globals.url_preview_domain_explicit_allowlist();
let denylist_domain_explicit = services().globals.url_preview_domain_explicit_denylist(); let denylist_domain_explicit = services.globals.url_preview_domain_explicit_denylist();
let allowlist_url_contains = services().globals.url_preview_url_contains_allowlist(); let allowlist_url_contains = services.globals.url_preview_url_contains_allowlist();
if allowlist_domain_contains.contains(&"*".to_owned()) if allowlist_domain_contains.contains(&"*".to_owned())
|| allowlist_domain_explicit.contains(&"*".to_owned()) || allowlist_domain_explicit.contains(&"*".to_owned())
@@ -735,7 +721,7 @@ fn url_preview_allowed(url_str: &str) -> bool {
} }
// check root domain if available and if user has root domain checks // check root domain if available and if user has root domain checks
if services().globals.url_preview_check_root_domain() { if services.globals.url_preview_check_root_domain() {
debug!("Checking root domain"); debug!("Checking root domain");
match host.split_once('.') { match host.split_once('.') {
None => return false, None => return false,
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use axum::extract::State;
use conduit::PduCount; use conduit::PduCount;
use ruma::{ use ruma::{
api::client::{ api::client::{
@@ -12,7 +13,10 @@ use ruma::{
}; };
use serde_json::{from_str, Value}; use serde_json::{from_str, Value};
use crate::{service::pdu::PduBuilder, services, utils, Error, PduEvent, Result, Ruma}; use crate::{
service::{pdu::PduBuilder, Services},
utils, Error, PduEvent, Result, Ruma,
};
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}` /// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
/// ///
@@ -24,21 +28,19 @@ use crate::{service::pdu::PduBuilder, services, utils, Error, PduEvent, Result,
/// - Tries to send the event into the room, auth rules will determine if it is /// - Tries to send the event into the room, auth rules will determine if it is
/// allowed /// allowed
pub(crate) async fn send_message_event_route( pub(crate) async fn send_message_event_route(
body: Ruma<send_message_event::v3::Request>, State(services): State<crate::State>, body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> { ) -> Result<send_message_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref(); let sender_device = body.sender_device.as_deref();
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await; let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Forbid m.room.encrypted if encryption is disabled // Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services().globals.allow_encryption() { if MessageLikeEventType::RoomEncrypted == body.event_type && !services.globals.allow_encryption() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
} }
if body.event_type == MessageLikeEventType::CallInvite if body.event_type == MessageLikeEventType::CallInvite && services.rooms.directory.is_public_room(&body.room_id)? {
&& services().rooms.directory.is_public_room(&body.room_id)?
{
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Room call invites are not allowed in public rooms", "Room call invites are not allowed in public rooms",
@@ -46,7 +48,7 @@ pub(crate) async fn send_message_event_route(
} }
// Check if this is a new transaction id // Check if this is a new transaction id
if let Some(response) = services() if let Some(response) = services
.transaction_ids .transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)? .existing_txnid(sender_user, sender_device, &body.txn_id)?
{ {
@@ -71,7 +73,7 @@ pub(crate) async fn send_message_event_route(
let mut unsigned = BTreeMap::new(); let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into()); unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let event_id = services() let event_id = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -89,7 +91,7 @@ pub(crate) async fn send_message_event_route(
) )
.await?; .await?;
services() services
.transaction_ids .transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?; .add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?;
@@ -105,7 +107,7 @@ pub(crate) async fn send_message_event_route(
/// - Only works if the user is joined (TODO: always allow, but only show events /// - Only works if the user is joined (TODO: always allow, but only show events
/// where the user was joined, depending on `history_visibility`) /// where the user was joined, depending on `history_visibility`)
pub(crate) async fn get_message_events_route( pub(crate) async fn get_message_events_route(
body: Ruma<get_message_events::v3::Request>, State(services): State<crate::State>, body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> { ) -> Result<get_message_events::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -123,7 +125,7 @@ pub(crate) async fn get_message_events_route(
.as_ref() .as_ref()
.and_then(|t| PduCount::try_from_string(t).ok()); .and_then(|t| PduCount::try_from_string(t).ok());
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from) .lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
@@ -139,12 +141,12 @@ pub(crate) async fn get_message_events_route(
match body.dir { match body.dir {
ruma::api::Direction::Forward => { ruma::api::Direction::Forward => {
let events_after: Vec<_> = services() let events_after: Vec<_> = services
.rooms .rooms
.timeline .timeline
.pdus_after(sender_user, &body.room_id, from)? .pdus_after(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events .filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id) .filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)
}) })
.take_while(|&(k, _)| Some(k) != to) // Stop at `to` .take_while(|&(k, _)| Some(k) != to) // Stop at `to`
@@ -157,7 +159,7 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034 * https://github.com/vector-im/element-web/issues/21034
*/ */
if !cfg!(feature = "element_hacks") if !cfg!(feature = "element_hacks")
&& !services().rooms.lazy_loading.lazy_load_was_sent_before( && !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
&body.room_id, &body.room_id,
@@ -181,17 +183,17 @@ pub(crate) async fn get_message_events_route(
resp.chunk = events_after; resp.chunk = events_after;
}, },
ruma::api::Direction::Backward => { ruma::api::Direction::Backward => {
services() services
.rooms .rooms
.timeline .timeline
.backfill_if_required(&body.room_id, from) .backfill_if_required(&body.room_id, from)
.await?; .await?;
let events_before: Vec<_> = services() let events_before: Vec<_> = services
.rooms .rooms
.timeline .timeline
.pdus_until(sender_user, &body.room_id, from)? .pdus_until(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events .filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id)}) .filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to` .take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.take(limit) .take(limit)
.collect(); .collect();
@@ -202,7 +204,7 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034 * https://github.com/vector-im/element-web/issues/21034
*/ */
if !cfg!(feature = "element_hacks") if !cfg!(feature = "element_hacks")
&& !services().rooms.lazy_loading.lazy_load_was_sent_before( && !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
&body.room_id, &body.room_id,
@@ -229,11 +231,12 @@ pub(crate) async fn get_message_events_route(
resp.state = Vec::new(); resp.state = Vec::new();
for ll_id in &lazy_loaded { for ll_id in &lazy_loaded {
if let Some(member_event) = services().rooms.state_accessor.room_state_get( if let Some(member_event) =
&body.room_id, services
&StateEventType::RoomMember, .rooms
ll_id.as_str(), .state_accessor
)? { .room_state_get(&body.room_id, &StateEventType::RoomMember, ll_id.as_str())?
{
resp.state.push(member_event.to_state_event()); resp.state.push(member_event.to_state_event());
} }
} }
@@ -241,7 +244,7 @@ pub(crate) async fn get_message_events_route(
// remove the feature check when we are sure clients like element can handle it // remove the feature check when we are sure clients like element can handle it
if !cfg!(feature = "element_hacks") { if !cfg!(feature = "element_hacks") {
if let Some(next_token) = next_token { if let Some(next_token) = next_token {
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token) .lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token)
@@ -252,8 +255,8 @@ pub(crate) async fn get_message_events_route(
Ok(resp) Ok(resp)
} }
fn visibility_filter(pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool { fn visibility_filter(services: &Services, pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
services() services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(user_id, room_id, &pdu.event_id) .user_can_see_event(user_id, room_id, &pdu.event_id)
+5 -4
View File
@@ -1,5 +1,6 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State;
use conduit::utils; use conduit::utils;
use ruma::{ use ruma::{
api::client::{account, error::ErrorKind}, api::client::{account, error::ErrorKind},
@@ -7,7 +8,7 @@ use ruma::{
}; };
use super::TOKEN_LENGTH; use super::TOKEN_LENGTH;
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `POST /_matrix/client/v3/user/{userId}/openid/request_token` /// # `POST /_matrix/client/v3/user/{userId}/openid/request_token`
/// ///
@@ -15,7 +16,7 @@ use crate::{services, Error, Result, Ruma};
/// ///
/// - The token generated is only valid for the OpenID API /// - The token generated is only valid for the OpenID API
pub(crate) async fn create_openid_token_route( pub(crate) async fn create_openid_token_route(
body: Ruma<account::request_openid_token::v3::Request>, State(services): State<crate::State>, body: Ruma<account::request_openid_token::v3::Request>,
) -> Result<account::request_openid_token::v3::Response> { ) -> Result<account::request_openid_token::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -28,14 +29,14 @@ pub(crate) async fn create_openid_token_route(
let access_token = utils::random_string(TOKEN_LENGTH); let access_token = utils::random_string(TOKEN_LENGTH);
let expires_in = services() let expires_in = services
.users .users
.create_openid_token(&body.user_id, &access_token)?; .create_openid_token(&body.user_id, &access_token)?;
Ok(account::request_openid_token::v3::Response { Ok(account::request_openid_token::v3::Response {
access_token, access_token,
token_type: TokenType::Bearer, token_type: TokenType::Bearer,
matrix_server_name: services().globals.config.server_name.clone(), matrix_server_name: services.globals.config.server_name.clone(),
expires_in: Duration::from_secs(expires_in), expires_in: Duration::from_secs(expires_in),
}) })
} }
+20 -8
View File
@@ -1,22 +1,32 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State;
use ruma::api::client::{ use ruma::api::client::{
error::ErrorKind, error::ErrorKind,
presence::{get_presence, set_presence}, presence::{get_presence, set_presence},
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/presence/{userId}/status` /// # `PUT /_matrix/client/r0/presence/{userId}/status`
/// ///
/// Sets the presence state of the sender user. /// Sets the presence state of the sender user.
pub(crate) async fn set_presence_route(body: Ruma<set_presence::v3::Request>) -> Result<set_presence::v3::Response> { pub(crate) async fn set_presence_route(
if !services().globals.allow_local_presence() { State(services): State<crate::State>, body: Ruma<set_presence::v3::Request>,
) -> Result<set_presence::v3::Response> {
if !services.globals.allow_local_presence() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
} }
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() if sender_user != &body.user_id && body.appservice_info.is_none() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Not allowed to set presence of other users",
));
}
services
.presence .presence
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())?; .set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())?;
@@ -28,8 +38,10 @@ pub(crate) async fn set_presence_route(body: Ruma<set_presence::v3::Request>) ->
/// Gets the presence state of the given user. /// Gets the presence state of the given user.
/// ///
/// - Only works if you share a room with the user /// - Only works if you share a room with the user
pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) -> Result<get_presence::v3::Response> { pub(crate) async fn get_presence_route(
if !services().globals.allow_local_presence() { State(services): State<crate::State>, body: Ruma<get_presence::v3::Request>,
) -> Result<get_presence::v3::Response> {
if !services.globals.allow_local_presence() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
} }
@@ -37,12 +49,12 @@ pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) ->
let mut presence_event = None; let mut presence_event = None;
for _room_id in services() for _room_id in services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])? .get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
{ {
if let Some(presence) = services().presence.get_presence(&body.user_id)? { if let Some(presence) = services.presence.get_presence(&body.user_id)? {
presence_event = Some(presence); presence_event = Some(presence);
break; break;
} }
+64 -59
View File
@@ -1,3 +1,5 @@
use axum::extract::State;
use conduit::{pdu::PduBuilder, warn, Error, Result};
use ruma::{ use ruma::{
api::{ api::{
client::{ client::{
@@ -11,12 +13,9 @@ use ruma::{
OwnedMxcUri, OwnedRoomId, OwnedUserId, OwnedMxcUri, OwnedRoomId, OwnedUserId,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use tracing::warn; use service::Services;
use crate::{ use crate::Ruma;
service::{pdu::PduBuilder, user_is_local},
services, Error, Result, Ruma,
};
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname` /// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
/// ///
@@ -24,21 +23,21 @@ use crate::{
/// ///
/// - Also makes sure other users receive the update using presence EDUs /// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_displayname_route( pub(crate) async fn set_displayname_route(
body: Ruma<set_display_name::v3::Request>, State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> { ) -> Result<set_display_name::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(sender_user)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?; update_displayname(&services, sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?;
if services().globals.allow_local_presence() { if services.globals.allow_local_presence() {
// Presence update // Presence update
services() services
.presence .presence
.ping_presence(sender_user, &PresenceState::Online)?; .ping_presence(sender_user, &PresenceState::Online)?;
} }
@@ -53,11 +52,11 @@ pub(crate) async fn set_displayname_route(
/// - If user is on another server and we do not have a local copy already fetch /// - If user is on another server and we do not have a local copy already fetch
/// displayname over federation /// displayname over federation
pub(crate) async fn get_displayname_route( pub(crate) async fn get_displayname_route(
body: Ruma<get_display_name::v3::Request>, State(services): State<crate::State>, body: Ruma<get_display_name::v3::Request>,
) -> Result<get_display_name::v3::Response> { ) -> Result<get_display_name::v3::Response> {
if !user_is_local(&body.user_id) { if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user // Create and update our local copy of the user
if let Ok(response) = services() if let Ok(response) = services
.sending .sending
.send_federation_request( .send_federation_request(
body.user_id.server_name(), body.user_id.server_name(),
@@ -68,19 +67,19 @@ pub(crate) async fn get_displayname_route(
) )
.await .await
{ {
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?; services.users.create(&body.user_id, None)?;
} }
services() services
.users .users
.set_displayname(&body.user_id, response.displayname.clone()) .set_displayname(&body.user_id, response.displayname.clone())
.await?; .await?;
services() services
.users .users
.set_avatar_url(&body.user_id, response.avatar_url.clone()) .set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?; .await?;
services() services
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()) .set_blurhash(&body.user_id, response.blurhash.clone())
.await?; .await?;
@@ -91,14 +90,14 @@ pub(crate) async fn get_displayname_route(
} }
} }
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
} }
Ok(get_display_name::v3::Response { Ok(get_display_name::v3::Response {
displayname: services().users.displayname(&body.user_id)?, displayname: services.users.displayname(&body.user_id)?,
}) })
} }
@@ -108,10 +107,10 @@ pub(crate) async fn get_displayname_route(
/// ///
/// - Also makes sure other users receive the update using presence EDUs /// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_avatar_url_route( pub(crate) async fn set_avatar_url_route(
body: Ruma<set_avatar_url::v3::Request>, State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> { ) -> Result<set_avatar_url::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(sender_user)
@@ -119,6 +118,7 @@ pub(crate) async fn set_avatar_url_route(
.collect(); .collect();
update_avatar_url( update_avatar_url(
&services,
sender_user.clone(), sender_user.clone(),
body.avatar_url.clone(), body.avatar_url.clone(),
body.blurhash.clone(), body.blurhash.clone(),
@@ -126,9 +126,9 @@ pub(crate) async fn set_avatar_url_route(
) )
.await?; .await?;
if services().globals.allow_local_presence() { if services.globals.allow_local_presence() {
// Presence update // Presence update
services() services
.presence .presence
.ping_presence(sender_user, &PresenceState::Online)?; .ping_presence(sender_user, &PresenceState::Online)?;
} }
@@ -143,11 +143,11 @@ pub(crate) async fn set_avatar_url_route(
/// - If user is on another server and we do not have a local copy already fetch /// - If user is on another server and we do not have a local copy already fetch
/// `avatar_url` and blurhash over federation /// `avatar_url` and blurhash over federation
pub(crate) async fn get_avatar_url_route( pub(crate) async fn get_avatar_url_route(
body: Ruma<get_avatar_url::v3::Request>, State(services): State<crate::State>, body: Ruma<get_avatar_url::v3::Request>,
) -> Result<get_avatar_url::v3::Response> { ) -> Result<get_avatar_url::v3::Response> {
if !user_is_local(&body.user_id) { if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user // Create and update our local copy of the user
if let Ok(response) = services() if let Ok(response) = services
.sending .sending
.send_federation_request( .send_federation_request(
body.user_id.server_name(), body.user_id.server_name(),
@@ -158,19 +158,19 @@ pub(crate) async fn get_avatar_url_route(
) )
.await .await
{ {
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?; services.users.create(&body.user_id, None)?;
} }
services() services
.users .users
.set_displayname(&body.user_id, response.displayname.clone()) .set_displayname(&body.user_id, response.displayname.clone())
.await?; .await?;
services() services
.users .users
.set_avatar_url(&body.user_id, response.avatar_url.clone()) .set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?; .await?;
services() services
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()) .set_blurhash(&body.user_id, response.blurhash.clone())
.await?; .await?;
@@ -182,15 +182,15 @@ pub(crate) async fn get_avatar_url_route(
} }
} }
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
} }
Ok(get_avatar_url::v3::Response { Ok(get_avatar_url::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?, avatar_url: services.users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?, blurhash: services.users.blurhash(&body.user_id)?,
}) })
} }
@@ -200,10 +200,12 @@ pub(crate) async fn get_avatar_url_route(
/// ///
/// - If user is on another server and we do not have a local copy already, /// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation. /// fetch profile over federation.
pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> Result<get_profile::v3::Response> { pub(crate) async fn get_profile_route(
if !user_is_local(&body.user_id) { State(services): State<crate::State>, body: Ruma<get_profile::v3::Request>,
) -> Result<get_profile::v3::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user // Create and update our local copy of the user
if let Ok(response) = services() if let Ok(response) = services
.sending .sending
.send_federation_request( .send_federation_request(
body.user_id.server_name(), body.user_id.server_name(),
@@ -214,19 +216,19 @@ pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> R
) )
.await .await
{ {
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?; services.users.create(&body.user_id, None)?;
} }
services() services
.users .users
.set_displayname(&body.user_id, response.displayname.clone()) .set_displayname(&body.user_id, response.displayname.clone())
.await?; .await?;
services() services
.users .users
.set_avatar_url(&body.user_id, response.avatar_url.clone()) .set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?; .await?;
services() services
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()) .set_blurhash(&body.user_id, response.blurhash.clone())
.await?; .await?;
@@ -239,23 +241,23 @@ pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> R
} }
} }
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist and we couldn't fetch it over // Return 404 if this user doesn't exist and we couldn't fetch it over
// federation // federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
} }
Ok(get_profile::v3::Response { Ok(get_profile::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?, avatar_url: services.users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?, blurhash: services.users.blurhash(&body.user_id)?,
displayname: services().users.displayname(&body.user_id)?, displayname: services.users.displayname(&body.user_id)?,
}) })
} }
pub async fn update_displayname( pub async fn update_displayname(
user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>, services: &Services, user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
) -> Result<()> { ) -> Result<()> {
services() services
.users .users
.set_displayname(&user_id, displayname.clone()) .set_displayname(&user_id, displayname.clone())
.await?; .await?;
@@ -271,7 +273,7 @@ pub async fn update_displayname(
displayname: displayname.clone(), displayname: displayname.clone(),
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
..serde_json::from_str( ..serde_json::from_str(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())? .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
@@ -294,19 +296,20 @@ pub async fn update_displayname(
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_all_rooms(all_joined_rooms, user_id).await; update_all_rooms(services, all_joined_rooms, user_id).await;
Ok(()) Ok(())
} }
pub async fn update_avatar_url( pub async fn update_avatar_url(
user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>, all_joined_rooms: Vec<OwnedRoomId>, services: &Services, user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
all_joined_rooms: Vec<OwnedRoomId>,
) -> Result<()> { ) -> Result<()> {
services() services
.users .users
.set_avatar_url(&user_id, avatar_url.clone()) .set_avatar_url(&user_id, avatar_url.clone())
.await?; .await?;
services() services
.users .users
.set_blurhash(&user_id, blurhash.clone()) .set_blurhash(&user_id, blurhash.clone())
.await?; .await?;
@@ -323,7 +326,7 @@ pub async fn update_avatar_url(
blurhash: blurhash.clone(), blurhash: blurhash.clone(),
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
..serde_json::from_str( ..serde_json::from_str(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())? .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
@@ -346,15 +349,17 @@ pub async fn update_avatar_url(
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_all_rooms(all_joined_rooms, user_id).await; update_all_rooms(services, all_joined_rooms, user_id).await;
Ok(()) Ok(())
} }
pub async fn update_all_rooms(all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId) { pub async fn update_all_rooms(
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId,
) {
for (pdu_builder, room_id) in all_joined_rooms { for (pdu_builder, room_id) in all_joined_rooms {
let state_lock = services().rooms.state.mutex.lock(room_id).await; let state_lock = services.rooms.state.mutex.lock(room_id).await;
if let Err(e) = services() if let Err(e) = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu(pdu_builder, &user_id, room_id, &state_lock) .build_and_append_pdu(pdu_builder, &user_id, room_id, &state_lock)
+35 -26
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -10,18 +11,18 @@ use ruma::{
push::{InsertPushRuleError, RemovePushRuleError, Ruleset}, push::{InsertPushRuleError, RemovePushRuleError, Ruleset},
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/pushrules/` /// # `GET /_matrix/client/r0/pushrules/`
/// ///
/// Retrieves the push rules event for this user. /// Retrieves the push rules event for this user.
pub(crate) async fn get_pushrules_all_route( pub(crate) async fn get_pushrules_all_route(
body: Ruma<get_pushrules_all::v3::Request>, State(services): State<crate::State>, body: Ruma<get_pushrules_all::v3::Request>,
) -> Result<get_pushrules_all::v3::Response> { ) -> Result<get_pushrules_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = let event =
services() services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?; .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?;
@@ -34,7 +35,7 @@ pub(crate) async fn get_pushrules_all_route(
global: account_data.global, global: account_data.global,
}) })
} else { } else {
services().account_data.update( services.account_data.update(
None, None,
sender_user, sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -55,10 +56,12 @@ pub(crate) async fn get_pushrules_all_route(
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}` /// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
/// ///
/// Retrieves a single specified push rule for this user. /// Retrieves a single specified push rule for this user.
pub(crate) async fn get_pushrule_route(body: Ruma<get_pushrule::v3::Request>) -> Result<get_pushrule::v3::Response> { pub(crate) async fn get_pushrule_route(
State(services): State<crate::State>, body: Ruma<get_pushrule::v3::Request>,
) -> Result<get_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -84,7 +87,9 @@ pub(crate) async fn get_pushrule_route(body: Ruma<get_pushrule::v3::Request>) ->
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}` /// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
/// ///
/// Creates a single specified push rule for this user. /// Creates a single specified push rule for this user.
pub(crate) async fn set_pushrule_route(body: Ruma<set_pushrule::v3::Request>) -> Result<set_pushrule::v3::Response> { pub(crate) async fn set_pushrule_route(
State(services): State<crate::State>, body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body; let body = body.body;
@@ -95,7 +100,7 @@ pub(crate) async fn set_pushrule_route(body: Ruma<set_pushrule::v3::Request>) ->
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -134,7 +139,7 @@ pub(crate) async fn set_pushrule_route(body: Ruma<set_pushrule::v3::Request>) ->
return Err(err); return Err(err);
} }
services().account_data.update( services.account_data.update(
None, None,
sender_user, sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -148,7 +153,7 @@ pub(crate) async fn set_pushrule_route(body: Ruma<set_pushrule::v3::Request>) ->
/// ///
/// Gets the actions of a single specified push rule for this user. /// Gets the actions of a single specified push rule for this user.
pub(crate) async fn get_pushrule_actions_route( pub(crate) async fn get_pushrule_actions_route(
body: Ruma<get_pushrule_actions::v3::Request>, State(services): State<crate::State>, body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> { ) -> Result<get_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -159,7 +164,7 @@ pub(crate) async fn get_pushrule_actions_route(
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -183,7 +188,7 @@ pub(crate) async fn get_pushrule_actions_route(
/// ///
/// Sets the actions of a single specified push rule for this user. /// Sets the actions of a single specified push rule for this user.
pub(crate) async fn set_pushrule_actions_route( pub(crate) async fn set_pushrule_actions_route(
body: Ruma<set_pushrule_actions::v3::Request>, State(services): State<crate::State>, body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> { ) -> Result<set_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -194,7 +199,7 @@ pub(crate) async fn set_pushrule_actions_route(
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -211,7 +216,7 @@ pub(crate) async fn set_pushrule_actions_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."));
} }
services().account_data.update( services.account_data.update(
None, None,
sender_user, sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -225,7 +230,7 @@ pub(crate) async fn set_pushrule_actions_route(
/// ///
/// Gets the enabled status of a single specified push rule for this user. /// Gets the enabled status of a single specified push rule for this user.
pub(crate) async fn get_pushrule_enabled_route( pub(crate) async fn get_pushrule_enabled_route(
body: Ruma<get_pushrule_enabled::v3::Request>, State(services): State<crate::State>, body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> { ) -> Result<get_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -236,7 +241,7 @@ pub(crate) async fn get_pushrule_enabled_route(
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -259,7 +264,7 @@ pub(crate) async fn get_pushrule_enabled_route(
/// ///
/// Sets the enabled status of a single specified push rule for this user. /// Sets the enabled status of a single specified push rule for this user.
pub(crate) async fn set_pushrule_enabled_route( pub(crate) async fn set_pushrule_enabled_route(
body: Ruma<set_pushrule_enabled::v3::Request>, State(services): State<crate::State>, body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> { ) -> Result<set_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -270,7 +275,7 @@ pub(crate) async fn set_pushrule_enabled_route(
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -287,7 +292,7 @@ pub(crate) async fn set_pushrule_enabled_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."));
} }
services().account_data.update( services.account_data.update(
None, None,
sender_user, sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -301,7 +306,7 @@ pub(crate) async fn set_pushrule_enabled_route(
/// ///
/// Deletes a single specified push rule for this user. /// Deletes a single specified push rule for this user.
pub(crate) async fn delete_pushrule_route( pub(crate) async fn delete_pushrule_route(
body: Ruma<delete_pushrule::v3::Request>, State(services): State<crate::State>, body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> { ) -> Result<delete_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -312,7 +317,7 @@ pub(crate) async fn delete_pushrule_route(
)); ));
} }
let event = services() let event = services
.account_data .account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())? .get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -336,7 +341,7 @@ pub(crate) async fn delete_pushrule_route(
return Err(err); return Err(err);
} }
services().account_data.update( services.account_data.update(
None, None,
sender_user, sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(), GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -349,11 +354,13 @@ pub(crate) async fn delete_pushrule_route(
/// # `GET /_matrix/client/r0/pushers` /// # `GET /_matrix/client/r0/pushers`
/// ///
/// Gets all currently active pushers for the sender user. /// Gets all currently active pushers for the sender user.
pub(crate) async fn get_pushers_route(body: Ruma<get_pushers::v3::Request>) -> Result<get_pushers::v3::Response> { pub(crate) async fn get_pushers_route(
State(services): State<crate::State>, body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(get_pushers::v3::Response { Ok(get_pushers::v3::Response {
pushers: services().pusher.get_pushers(sender_user)?, pushers: services.pusher.get_pushers(sender_user)?,
}) })
} }
@@ -362,10 +369,12 @@ pub(crate) async fn get_pushers_route(body: Ruma<get_pushers::v3::Request>) -> R
/// Adds a pusher for the sender user. /// Adds a pusher for the sender user.
/// ///
/// - TODO: Handle `append` /// - TODO: Handle `append`
pub(crate) async fn set_pushers_route(body: Ruma<set_pusher::v3::Request>) -> Result<set_pusher::v3::Response> { pub(crate) async fn set_pushers_route(
State(services): State<crate::State>, body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services().pusher.set_pusher(sender_user, &body.action)?; services.pusher.set_pusher(sender_user, &body.action)?;
Ok(set_pusher::v3::Response::default()) Ok(set_pusher::v3::Response::default())
} }
+14 -13
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State;
use conduit::PduCount; use conduit::PduCount;
use ruma::{ use ruma::{
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt}, api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
@@ -10,7 +11,7 @@ use ruma::{
MilliSecondsSinceUnixEpoch, MilliSecondsSinceUnixEpoch,
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers` /// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers`
/// ///
@@ -20,7 +21,7 @@ use crate::{services, Error, Result, Ruma};
/// - If `read_receipt` is set: Update private marker and public read receipt /// - If `read_receipt` is set: Update private marker and public read receipt
/// EDU /// EDU
pub(crate) async fn set_read_marker_route( pub(crate) async fn set_read_marker_route(
body: Ruma<set_read_marker::v3::Request>, State(services): State<crate::State>, body: Ruma<set_read_marker::v3::Request>,
) -> Result<set_read_marker::v3::Response> { ) -> Result<set_read_marker::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -30,7 +31,7 @@ pub(crate) async fn set_read_marker_route(
event_id: fully_read.clone(), event_id: fully_read.clone(),
}, },
}; };
services().account_data.update( services.account_data.update(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::FullyRead, RoomAccountDataEventType::FullyRead,
@@ -39,14 +40,14 @@ pub(crate) async fn set_read_marker_route(
} }
if body.private_read_receipt.is_some() || body.read_receipt.is_some() { if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
services() services
.rooms .rooms
.user .user
.reset_notification_counts(sender_user, &body.room_id)?; .reset_notification_counts(sender_user, &body.room_id)?;
} }
if let Some(event) = &body.private_read_receipt { if let Some(event) = &body.private_read_receipt {
let count = services() let count = services
.rooms .rooms
.timeline .timeline
.get_pdu_count(event)? .get_pdu_count(event)?
@@ -60,7 +61,7 @@ pub(crate) async fn set_read_marker_route(
}, },
PduCount::Normal(c) => c, PduCount::Normal(c) => c,
}; };
services() services
.rooms .rooms
.read_receipt .read_receipt
.private_read_set(&body.room_id, sender_user, count)?; .private_read_set(&body.room_id, sender_user, count)?;
@@ -82,7 +83,7 @@ pub(crate) async fn set_read_marker_route(
let mut receipt_content = BTreeMap::new(); let mut receipt_content = BTreeMap::new();
receipt_content.insert(event.to_owned(), receipts); receipt_content.insert(event.to_owned(), receipts);
services().rooms.read_receipt.readreceipt_update( services.rooms.read_receipt.readreceipt_update(
sender_user, sender_user,
&body.room_id, &body.room_id,
&ruma::events::receipt::ReceiptEvent { &ruma::events::receipt::ReceiptEvent {
@@ -99,7 +100,7 @@ pub(crate) async fn set_read_marker_route(
/// ///
/// Sets private read marker and public read receipt EDU. /// Sets private read marker and public read receipt EDU.
pub(crate) async fn create_receipt_route( pub(crate) async fn create_receipt_route(
body: Ruma<create_receipt::v3::Request>, State(services): State<crate::State>, body: Ruma<create_receipt::v3::Request>,
) -> Result<create_receipt::v3::Response> { ) -> Result<create_receipt::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -107,7 +108,7 @@ pub(crate) async fn create_receipt_route(
&body.receipt_type, &body.receipt_type,
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
) { ) {
services() services
.rooms .rooms
.user .user
.reset_notification_counts(sender_user, &body.room_id)?; .reset_notification_counts(sender_user, &body.room_id)?;
@@ -120,7 +121,7 @@ pub(crate) async fn create_receipt_route(
event_id: body.event_id.clone(), event_id: body.event_id.clone(),
}, },
}; };
services().account_data.update( services.account_data.update(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::FullyRead, RoomAccountDataEventType::FullyRead,
@@ -142,7 +143,7 @@ pub(crate) async fn create_receipt_route(
let mut receipt_content = BTreeMap::new(); let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.clone(), receipts); receipt_content.insert(body.event_id.clone(), receipts);
services().rooms.read_receipt.readreceipt_update( services.rooms.read_receipt.readreceipt_update(
sender_user, sender_user,
&body.room_id, &body.room_id,
&ruma::events::receipt::ReceiptEvent { &ruma::events::receipt::ReceiptEvent {
@@ -152,7 +153,7 @@ pub(crate) async fn create_receipt_route(
)?; )?;
}, },
create_receipt::v3::ReceiptType::ReadPrivate => { create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services() let count = services
.rooms .rooms
.timeline .timeline
.get_pdu_count(&body.event_id)? .get_pdu_count(&body.event_id)?
@@ -166,7 +167,7 @@ pub(crate) async fn create_receipt_route(
}, },
PduCount::Normal(c) => c, PduCount::Normal(c) => c,
}; };
services() services
.rooms .rooms
.read_receipt .read_receipt
.private_read_set(&body.room_id, sender_user, count)?; .private_read_set(&body.room_id, sender_user, count)?;
+7 -4
View File
@@ -1,23 +1,26 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::redact::redact_event, api::client::redact::redact_event,
events::{room::redaction::RoomRedactionEventContent, TimelineEventType}, events::{room::redaction::RoomRedactionEventContent, TimelineEventType},
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use crate::{service::pdu::PduBuilder, services, Result, Ruma}; use crate::{service::pdu::PduBuilder, Result, Ruma};
/// # `PUT /_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}` /// # `PUT /_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}`
/// ///
/// Tries to send a redaction event into the room. /// Tries to send a redaction event into the room.
/// ///
/// - TODO: Handle txn id /// - TODO: Handle txn id
pub(crate) async fn redact_event_route(body: Ruma<redact_event::v3::Request>) -> Result<redact_event::v3::Response> { pub(crate) async fn redact_event_route(
State(services): State<crate::State>, body: Ruma<redact_event::v3::Request>,
) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body; let body = body.body;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await; let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let event_id = services() let event_id = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
+41 -49
View File
@@ -1,30 +1,28 @@
use axum::extract::State;
use ruma::api::client::relations::{ use ruma::api::client::relations::{
get_relating_events, get_relating_events_with_rel_type, get_relating_events_with_rel_type_and_event_type, get_relating_events, get_relating_events_with_rel_type, get_relating_events_with_rel_type_and_event_type,
}; };
use crate::{services, Result, Ruma}; use crate::{Result, Ruma};
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}` /// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route( pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>, State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> { ) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let res = services() let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
.rooms sender_user,
.pdu_metadata &body.room_id,
.paginate_relations_with_filter( &body.event_id,
sender_user, &Some(body.event_type.clone()),
&body.room_id, &Some(body.rel_type.clone()),
&body.event_id, &body.from,
&Some(body.event_type.clone()), &body.to,
&Some(body.rel_type.clone()), &body.limit,
&body.from, body.recurse,
&body.to, body.dir,
&body.limit, )?;
body.recurse,
body.dir,
)?;
Ok(get_relating_events_with_rel_type_and_event_type::v1::Response { Ok(get_relating_events_with_rel_type_and_event_type::v1::Response {
chunk: res.chunk, chunk: res.chunk,
@@ -36,25 +34,22 @@ pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}` /// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}`
pub(crate) async fn get_relating_events_with_rel_type_route( pub(crate) async fn get_relating_events_with_rel_type_route(
body: Ruma<get_relating_events_with_rel_type::v1::Request>, State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type::v1::Response> { ) -> Result<get_relating_events_with_rel_type::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let res = services() let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
.rooms sender_user,
.pdu_metadata &body.room_id,
.paginate_relations_with_filter( &body.event_id,
sender_user, &None,
&body.room_id, &Some(body.rel_type.clone()),
&body.event_id, &body.from,
&None, &body.to,
&Some(body.rel_type.clone()), &body.limit,
&body.from, body.recurse,
&body.to, body.dir,
&body.limit, )?;
body.recurse,
body.dir,
)?;
Ok(get_relating_events_with_rel_type::v1::Response { Ok(get_relating_events_with_rel_type::v1::Response {
chunk: res.chunk, chunk: res.chunk,
@@ -66,23 +61,20 @@ pub(crate) async fn get_relating_events_with_rel_type_route(
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}` /// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}`
pub(crate) async fn get_relating_events_route( pub(crate) async fn get_relating_events_route(
body: Ruma<get_relating_events::v1::Request>, State(services): State<crate::State>, body: Ruma<get_relating_events::v1::Request>,
) -> Result<get_relating_events::v1::Response> { ) -> Result<get_relating_events::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services() services.rooms.pdu_metadata.paginate_relations_with_filter(
.rooms sender_user,
.pdu_metadata &body.room_id,
.paginate_relations_with_filter( &body.event_id,
sender_user, &None,
&body.room_id, &None,
&body.event_id, &body.from,
&None, &body.to,
&None, &body.limit,
&body.from, body.recurse,
&body.to, body.dir,
&body.limit, )
body.recurse,
body.dir,
)
} }
+22 -8
View File
@@ -1,5 +1,6 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State;
use rand::Rng; use rand::Rng;
use ruma::{ use ruma::{
api::client::{error::ErrorKind, room::report_content}, api::client::{error::ErrorKind, room::report_content},
@@ -9,13 +10,18 @@ use ruma::{
use tokio::time::sleep; use tokio::time::sleep;
use tracing::info; use tracing::info;
use crate::{debug_info, service::pdu::PduEvent, services, utils::HtmlEscape, Error, Result, Ruma}; use crate::{
debug_info,
service::{pdu::PduEvent, Services},
utils::HtmlEscape,
Error, Result, Ruma,
};
/// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}` /// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}`
/// ///
/// Reports an inappropriate event to homeserver admins /// Reports an inappropriate event to homeserver admins
pub(crate) async fn report_event_route( pub(crate) async fn report_event_route(
body: Ruma<report_content::v3::Request>, State(services): State<crate::State>, body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> { ) -> Result<report_content::v3::Response> {
// user authentication // user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -26,18 +32,26 @@ pub(crate) async fn report_event_route(
); );
// check if we know about the reported event ID or if it's invalid // check if we know about the reported event ID or if it's invalid
let Some(pdu) = services().rooms.timeline.get_pdu(&body.event_id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&body.event_id)? else {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"Event ID is not known to us or Event ID is invalid", "Event ID is not known to us or Event ID is invalid",
)); ));
}; };
is_report_valid(&pdu.event_id, &body.room_id, sender_user, &body.reason, body.score, &pdu)?; is_report_valid(
&services,
&pdu.event_id,
&body.room_id,
sender_user,
&body.reason,
body.score,
&pdu,
)?;
// send admin room message that we received the report with an @room ping for // send admin room message that we received the report with an @room ping for
// urgency // urgency
services() services
.admin .admin
.send_message(message::RoomMessageEventContent::text_html( .send_message(message::RoomMessageEventContent::text_html(
format!( format!(
@@ -79,8 +93,8 @@ pub(crate) async fn report_event_route(
/// check if score is in valid range /// check if score is in valid range
/// check if report reasoning is less than or equal to 750 characters /// check if report reasoning is less than or equal to 750 characters
fn is_report_valid( fn is_report_valid(
event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>, score: Option<ruma::Int>, services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>,
pdu: &std::sync::Arc<PduEvent>, score: Option<ruma::Int>, pdu: &std::sync::Arc<PduEvent>,
) -> Result<bool> { ) -> Result<bool> {
debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid"); debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid");
@@ -91,7 +105,7 @@ fn is_report_valid(
)); ));
} }
if !services() if !services
.rooms .rooms
.state_cache .state_cache
.room_members(&pdu.room_id) .room_members(&pdu.room_id)
+169 -100
View File
@@ -1,6 +1,7 @@
use std::{cmp::max, collections::BTreeMap}; use std::{cmp::max, collections::BTreeMap};
use conduit::{debug_info, debug_warn}; use axum::extract::State;
use conduit::{debug_info, debug_warn, err};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -19,6 +20,7 @@ use ruma::{
tombstone::RoomTombstoneEventContent, tombstone::RoomTombstoneEventContent,
topic::RoomTopicEventContent, topic::RoomTopicEventContent,
}, },
space::child::SpaceChildEventContent,
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
}, },
int, int,
@@ -30,8 +32,8 @@ use tracing::{error, info, warn};
use super::invite_helper; use super::invite_helper;
use crate::{ use crate::{
service::{appservice::RegistrationInfo, pdu::PduBuilder}, service::{appservice::RegistrationInfo, pdu::PduBuilder, Services},
services, Error, Result, Ruma, Error, Result, Ruma,
}; };
/// Recommended transferable state events list from the spec /// Recommended transferable state events list from the spec
@@ -63,44 +65,46 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
/// - Send events listed in initial state /// - Send events listed in initial state
/// - Send events implied by `name` and `topic` /// - Send events implied by `name` and `topic`
/// - Send invite events /// - Send invite events
pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> Result<create_room::v3::Response> { pub(crate) async fn create_room_route(
State(services): State<crate::State>, body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset; use create_room::v3::RoomPreset;
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services().globals.allow_room_creation() if !services.globals.allow_room_creation()
&& body.appservice_info.is_none() && body.appservice_info.is_none()
&& !services().users.is_admin(sender_user)? && !services.users.is_admin(sender_user)?
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled."));
} }
let room_id: OwnedRoomId = if let Some(custom_room_id) = &body.room_id { let room_id: OwnedRoomId = if let Some(custom_room_id) = &body.room_id {
custom_room_id_check(custom_room_id)? custom_room_id_check(&services, custom_room_id)?
} else { } else {
RoomId::new(&services().globals.config.server_name) RoomId::new(&services.globals.config.server_name)
}; };
// check if room ID doesn't already exist instead of erroring on auth check // check if room ID doesn't already exist instead of erroring on auth check
if services().rooms.short.get_shortroomid(&room_id)?.is_some() { if services.rooms.short.get_shortroomid(&room_id)?.is_some() {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::RoomInUse, ErrorKind::RoomInUse,
"Room with that custom room ID already exists", "Room with that custom room ID already exists",
)); ));
} }
let _short_id = services().rooms.short.get_or_create_shortroomid(&room_id)?; let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
let state_lock = services().rooms.state.mutex.lock(&room_id).await; let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name { let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name {
Some(room_alias_check(alias, &body.appservice_info).await?) Some(room_alias_check(&services, alias, &body.appservice_info).await?)
} else { } else {
None None
}; };
let room_version = match body.room_version.clone() { let room_version = match body.room_version.clone() {
Some(room_version) => { Some(room_version) => {
if services() if services
.globals .globals
.supported_room_versions() .supported_room_versions()
.contains(&room_version) .contains(&room_version)
@@ -113,7 +117,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
)); ));
} }
}, },
None => services().globals.default_room_version(), None => services.globals.default_room_version(),
}; };
let content = match &body.creation_content { let content = match &body.creation_content {
@@ -184,7 +188,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
}; };
// 1. The room create event // 1. The room create event
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -202,7 +206,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?; .await?;
// 2. Let the room creator join // 2. Let the room creator join
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -210,11 +214,11 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
event_type: TimelineEventType::RoomMember, event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent { content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join, membership: MembershipState::Join,
displayname: services().users.displayname(sender_user)?, displayname: services.users.displayname(sender_user)?,
avatar_url: services().users.avatar_url(sender_user)?, avatar_url: services.users.avatar_url(sender_user)?,
is_direct: Some(body.is_direct), is_direct: Some(body.is_direct),
third_party_invite: None, third_party_invite: None,
blurhash: services().users.blurhash(sender_user)?, blurhash: services.users.blurhash(sender_user)?,
reason: None, reason: None,
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
}) })
@@ -249,7 +253,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
let power_levels_content = let power_levels_content =
default_power_levels_content(&body.power_level_content_override, &body.visibility, users)?; default_power_levels_content(&body.power_level_content_override, &body.visibility, users)?;
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -268,7 +272,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 4. Canonical room alias // 4. Canonical room alias
if let Some(room_alias_id) = &alias { if let Some(room_alias_id) = &alias {
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -293,7 +297,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 5. Events set by preset // 5. Events set by preset
// 5.1 Join Rules // 5.1 Join Rules
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -316,7 +320,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?; .await?;
// 5.2 History Visibility // 5.2 History Visibility
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -335,7 +339,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?; .await?;
// 5.3 Guest Access // 5.3 Guest Access
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -378,11 +382,11 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
pdu_builder.state_key.get_or_insert_with(String::new); pdu_builder.state_key.get_or_insert_with(String::new);
// Silently skip encryption events if they are not allowed // Silently skip encryption events if they are not allowed
if pdu_builder.event_type == TimelineEventType::RoomEncryption && !services().globals.allow_encryption() { if pdu_builder.event_type == TimelineEventType::RoomEncryption && !services.globals.allow_encryption() {
continue; continue;
} }
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock) .build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
@@ -391,7 +395,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 7. Events implied by name and topic // 7. Events implied by name and topic
if let Some(name) = &body.name { if let Some(name) = &body.name {
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -411,7 +415,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
} }
if let Some(topic) = &body.topic { if let Some(topic) = &body.topic {
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -435,21 +439,21 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 8. Events implied by invite (and TODO: invite_3pid) // 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock); drop(state_lock);
for user_id in &body.invite { for user_id in &body.invite {
if let Err(e) = invite_helper(sender_user, user_id, &room_id, None, body.is_direct).await { if let Err(e) = invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct).await {
warn!(%e, "Failed to send invite"); warn!(%e, "Failed to send invite");
} }
} }
// Homeserver specific stuff // Homeserver specific stuff
if let Some(alias) = alias { if let Some(alias) = alias {
services() services
.rooms .rooms
.alias .alias
.set_alias(&alias, &room_id, sender_user)?; .set_alias(&alias, &room_id, sender_user)?;
} }
if body.visibility == room::Visibility::Public { if body.visibility == room::Visibility::Public {
services().rooms.directory.set_public(&room_id)?; services.rooms.directory.set_public(&room_id)?;
} }
info!("{sender_user} created a room with room ID {room_id}"); info!("{sender_user} created a room with room ID {room_id}");
@@ -464,20 +468,17 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
/// - You have to currently be joined to the room (TODO: Respect history /// - You have to currently be joined to the room (TODO: Respect history
/// visibility) /// visibility)
pub(crate) async fn get_room_event_route( pub(crate) async fn get_room_event_route(
body: Ruma<get_room_event::v3::Request>, State(services): State<crate::State>, body: Ruma<get_room_event::v3::Request>,
) -> Result<get_room_event::v3::Response> { ) -> Result<get_room_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services() let event = services
.rooms .rooms
.timeline .timeline
.get_pdu(&body.event_id)? .get_pdu(&body.event_id)?
.ok_or_else(|| { .ok_or_else(|| err!(Request(NotFound("Event {} not found.", &body.event_id))))?;
warn!("Event not found, event ID: {:?}", &body.event_id);
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
})?;
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &event.room_id, &body.event_id)? .user_can_see_event(sender_user, &event.room_id, &body.event_id)?
@@ -502,10 +503,12 @@ pub(crate) async fn get_room_event_route(
/// ///
/// - Only users joined to the room are allowed to call this, or if /// - Only users joined to the room are allowed to call this, or if
/// `history_visibility` is world readable in the room /// `history_visibility` is world readable in the room
pub(crate) async fn get_room_aliases_route(body: Ruma<aliases::v3::Request>) -> Result<aliases::v3::Response> { pub(crate) async fn get_room_aliases_route(
State(services): State<crate::State>, body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_state_events(sender_user, &body.room_id)? .user_can_see_state_events(sender_user, &body.room_id)?
@@ -517,7 +520,7 @@ pub(crate) async fn get_room_aliases_route(body: Ruma<aliases::v3::Request>) ->
} }
Ok(aliases::v3::Response { Ok(aliases::v3::Response {
aliases: services() aliases: services
.rooms .rooms
.alias .alias
.local_aliases_for_room(&body.room_id) .local_aliases_for_room(&body.room_id)
@@ -536,10 +539,12 @@ pub(crate) async fn get_room_aliases_route(body: Ruma<aliases::v3::Request>) ->
/// - Transfers some state events /// - Transfers some state events
/// - Moves local aliases /// - Moves local aliases
/// - Modifies old room power levels to prevent users from speaking /// - Modifies old room power levels to prevent users from speaking
pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) -> Result<upgrade_room::v3::Response> { pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>, body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services() if !services
.globals .globals
.supported_room_versions() .supported_room_versions()
.contains(&body.new_version) .contains(&body.new_version)
@@ -551,19 +556,19 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
} }
// Create a replacement room // Create a replacement room
let replacement_room = RoomId::new(services().globals.server_name()); let replacement_room = RoomId::new(services.globals.server_name());
let _short_id = services() let _short_id = services
.rooms .rooms
.short .short
.get_or_create_shortroomid(&replacement_room)?; .get_or_create_shortroomid(&replacement_room)?;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).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 // 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 // intended to be used any further Fail if the sender does not have the required
// permissions // permissions
let tombstone_event_id = services() let tombstone_event_id = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -586,11 +591,11 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Change lock to replacement room // Change lock to replacement room
drop(state_lock); drop(state_lock);
let state_lock = services().rooms.state.mutex.lock(&replacement_room).await; let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
// Get the old room creation event // Get the old room creation event
let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>( let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")? .room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
@@ -614,22 +619,14 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => { V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
create_event_content.insert( create_event_content.insert(
"creator".into(), "creator".into(),
json!(&sender_user).try_into().map_err(|e| { json!(&sender_user)
info!("Error forming creation event: {e}"); .try_into()
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event") .map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
})?,
); );
}, },
V11 => {
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
_ => { _ => {
warn!("Unexpected or unsupported room version {}", body.new_version); // "creator" key no longer exists in V11+ rooms
return Err(Error::BadRequest( create_event_content.remove("creator");
ErrorKind::BadJson,
"Unexpected or unsupported room version found",
));
}, },
} }
} }
@@ -638,27 +635,101 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
"room_version".into(), "room_version".into(),
json!(&body.new_version) json!(&body.new_version)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
); );
create_event_content.insert( create_event_content.insert(
"predecessor".into(), "predecessor".into(),
json!(predecessor) json!(predecessor)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
); );
// Validate creation event content // if the room was a space:
if serde_json::from_str::<CanonicalJsonObject>( // - migrate m.space.child and/or m.space.parent
to_raw_value(&create_event_content) // - add space type to replacement room m.room.create
.expect("Error forming creation event") //
.get(), // as apart of MSC4168
) if services
.is_err() .rooms
.state_accessor
.get_room_type(&body.room_id)
.unwrap_or(None)
.is_some_and(|room_type| room_type == ruma::room::RoomType::Space)
{ {
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")); create_event_content.insert(
"type".into(),
json!(ruma::room::RoomType::Space)
.try_into()
.map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
);
if let Some(event_content) = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::SpaceChild, body.room_id.as_str())
.unwrap_or(None)
{
// space contents that are empty are not apart of the space
if !event_content.content.get().is_empty() {
// remove all but us from via
let mut new_content: SpaceChildEventContent = serde_json::from_str(event_content.content.get())
.map_err(|_| err!(Database(error!("Invalid m.space.child content in database"))))?;
new_content.via = vec![services.globals.config.server_name.clone()];
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.to_string().into(),
content: to_raw_value(&new_content).expect("we just created it"),
unsigned: None,
state_key: Some(replacement_room.to_string()),
redacts: None,
},
sender_user,
&replacement_room,
&state_lock,
)
.await?;
}
}
if let Some(event_content) = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::SpaceParent, body.room_id.as_str())
.unwrap_or(None)
{
// space contents that are empty are not apart of the space
if !event_content.content.get().is_empty() {
// remove all but us from via
let mut new_content: SpaceChildEventContent = serde_json::from_str(event_content.content.get())
.map_err(|_| err!(Database(error!("Invalid m.space.child content in database"))))?;
new_content.via = vec![services.globals.config.server_name.clone()];
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceParent.to_string().into(),
content: to_raw_value(&new_content).expect("we just created it"),
unsigned: None,
state_key: Some(replacement_room.to_string()),
redacts: None,
},
sender_user,
&replacement_room,
&state_lock,
)
.await?;
}
}
} }
services() // m.room.create
services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -676,7 +747,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
.await?; .await?;
// Join the new room // Join the new room
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -684,11 +755,11 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
event_type: TimelineEventType::RoomMember, event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent { content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join, membership: MembershipState::Join,
displayname: services().users.displayname(sender_user)?, displayname: services.users.displayname(sender_user)?,
avatar_url: services().users.avatar_url(sender_user)?, avatar_url: services.users.avatar_url(sender_user)?,
is_direct: None, is_direct: None,
third_party_invite: None, third_party_invite: None,
blurhash: services().users.blurhash(sender_user)?, blurhash: services.users.blurhash(sender_user)?,
reason: None, reason: None,
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
}) })
@@ -705,7 +776,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Replicate transferable state events to the new room // Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS { for event_type in TRANSFERABLE_STATE_EVENTS {
let event_content = match services() let event_content = match services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&body.room_id, event_type, "")? .room_state_get(&body.room_id, event_type, "")?
@@ -714,7 +785,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
None => continue, // Skipping missing events. None => continue, // Skipping missing events.
}; };
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -733,13 +804,13 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
} }
// Moves any local aliases to the new room // Moves any local aliases to the new room
for alias in services() for alias in services
.rooms .rooms
.alias .alias
.local_aliases_for_room(&body.room_id) .local_aliases_for_room(&body.room_id)
.filter_map(Result::ok) .filter_map(Result::ok)
{ {
services() services
.rooms .rooms
.alias .alias
.set_alias(&alias, &replacement_room, sender_user)?; .set_alias(&alias, &replacement_room, sender_user)?;
@@ -747,15 +818,15 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Get the old room power levels // Get the old room power levels
let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str( let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")? .room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? .ok_or_else(|| err!(Database(error!("Found room without m.room.create event."))))?
.content .content
.get(), .get(),
) )
.map_err(|_| Error::bad_database("Invalid room event in database."))?; .map_err(|_| err!(Database(error!("Invalid room event in database."))))?;
// Setting events_default and invite to the greater of 50 and users_default + 1 // Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max( let new_level = max(
@@ -763,16 +834,14 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
power_levels_event_content power_levels_event_content
.users_default .users_default
.checked_add(int!(1)) .checked_add(int!(1))
.ok_or_else(|| { .ok_or_else(|| err!(Request(BadJson("users_default power levels event content is not valid"))))?,
Error::BadRequest(ErrorKind::BadJson, "users_default power levels event content is not valid")
})?,
); );
power_levels_event_content.events_default = new_level; power_levels_event_content.events_default = new_level;
power_levels_event_content.invite = new_level; power_levels_event_content.invite = new_level;
// Modify the power levels in the old room to prevent sending of events and // Modify the power levels in the old room to prevent sending of events and
// inviting new users // inviting new users
services() services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -829,7 +898,7 @@ fn default_power_levels_content(
if let Some(power_level_content_override) = power_level_content_override { if let Some(power_level_content_override) = power_level_content_override {
let json: JsonObject = serde_json::from_str(power_level_content_override.json().get()) let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override."))?; .map_err(|_| err!(Request(BadJson("Invalid power_level_content_override"))))?;
for (key, value) in json { for (key, value) in json {
power_levels_content[key] = value; power_levels_content[key] = value;
@@ -841,7 +910,7 @@ fn default_power_levels_content(
/// if a room is being created with a room alias, run our checks /// if a room is being created with a room alias, run our checks
async fn room_alias_check( async fn room_alias_check(
room_alias_name: &str, appservice_info: &Option<RegistrationInfo>, services: &Services, room_alias_name: &str, appservice_info: &Option<RegistrationInfo>,
) -> Result<OwnedRoomAliasId> { ) -> Result<OwnedRoomAliasId> {
// Basic checks on the room alias validity // Basic checks on the room alias validity
if room_alias_name.contains(':') { if room_alias_name.contains(':') {
@@ -858,7 +927,7 @@ async fn room_alias_check(
} }
// check if room alias is forbidden // check if room alias is forbidden
if services() if services
.globals .globals
.forbidden_alias_names() .forbidden_alias_names()
.is_match(room_alias_name) .is_match(room_alias_name)
@@ -866,13 +935,13 @@ async fn room_alias_check(
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden.")); return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden."));
} }
let full_room_alias = RoomAliasId::parse(format!("#{}:{}", room_alias_name, services().globals.config.server_name)) let full_room_alias = RoomAliasId::parse(format!("#{}:{}", room_alias_name, services.globals.config.server_name))
.map_err(|e| { .map_err(|e| {
info!("Failed to parse room alias {room_alias_name}: {e}"); info!("Failed to parse room alias {room_alias_name}: {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.") Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.")
})?; })?;
if services() if services
.rooms .rooms
.alias .alias
.resolve_local_alias(&full_room_alias)? .resolve_local_alias(&full_room_alias)?
@@ -885,7 +954,7 @@ async fn room_alias_check(
if !info.aliases.is_match(full_room_alias.as_str()) { if !info.aliases.is_match(full_room_alias.as_str()) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace.")); return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
} }
} else if services() } else if services
.appservice .appservice
.is_exclusive_alias(&full_room_alias) .is_exclusive_alias(&full_room_alias)
.await .await
@@ -899,9 +968,9 @@ async fn room_alias_check(
} }
/// if a room is being created with a custom room ID, run our checks against it /// if a room is being created with a custom room ID, run our checks against it
fn custom_room_id_check(custom_room_id: &str) -> Result<OwnedRoomId> { fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
// apply forbidden room alias checks to custom room IDs too // apply forbidden room alias checks to custom room IDs too
if services() if services
.globals .globals
.forbidden_alias_names() .forbidden_alias_names()
.is_match(custom_room_id) .is_match(custom_room_id)
@@ -922,7 +991,7 @@ fn custom_room_id_check(custom_room_id: &str) -> Result<OwnedRoomId> {
)); ));
} }
let full_room_id = format!("!{}:{}", custom_room_id, services().globals.config.server_name); let full_room_id = format!("!{}:{}", custom_room_id, services.globals.config.server_name);
debug_info!("Full custom room ID: {full_room_id}"); debug_info!("Full custom room ID: {full_room_id}");
+13 -18
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -14,7 +15,7 @@ use ruma::{
}; };
use tracing::debug; use tracing::debug;
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `POST /_matrix/client/r0/search` /// # `POST /_matrix/client/r0/search`
/// ///
@@ -22,7 +23,9 @@ use crate::{services, Error, Result, Ruma};
/// ///
/// - Only works if the user is currently joined to the room (TODO: Respect /// - Only works if the user is currently joined to the room (TODO: Respect
/// history visibility) /// history visibility)
pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>) -> Result<search_events::v3::Response> { pub(crate) async fn search_events_route(
State(services): State<crate::State>, body: Ruma<search_events::v3::Request>,
) -> Result<search_events::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let search_criteria = body.search_categories.room_events.as_ref().unwrap(); let search_criteria = body.search_categories.room_events.as_ref().unwrap();
@@ -30,7 +33,7 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
let include_state = &search_criteria.include_state; let include_state = &search_criteria.include_state;
let room_ids = filter.rooms.clone().unwrap_or_else(|| { let room_ids = filter.rooms.clone().unwrap_or_else(|| {
services() services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(sender_user)
@@ -50,11 +53,7 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
if include_state.is_some_and(|include_state| include_state) { if include_state.is_some_and(|include_state| include_state) {
for room_id in &room_ids { for room_id in &room_ids {
if !services() if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
.rooms
.state_cache
.is_joined(sender_user, room_id)?
{
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"You don't have permission to view this room.", "You don't have permission to view this room.",
@@ -62,12 +61,12 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
} }
// check if sender_user can see state events // check if sender_user can see state events
if services() if services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_state_events(sender_user, room_id)? .user_can_see_state_events(sender_user, room_id)?
{ {
let room_state = services() let room_state = services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full(room_id) .room_state_full(room_id)
@@ -91,18 +90,14 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
let mut searches = Vec::new(); let mut searches = Vec::new();
for room_id in &room_ids { for room_id in &room_ids {
if !services() if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
.rooms
.state_cache
.is_joined(sender_user, room_id)?
{
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"You don't have permission to view this room.", "You don't have permission to view this room.",
)); ));
} }
if let Some(search) = services() if let Some(search) = services
.rooms .rooms
.search .search
.search_pdus(room_id, &search_criteria.search_term)? .search_pdus(room_id, &search_criteria.search_term)?
@@ -135,14 +130,14 @@ pub(crate) async fn search_events_route(body: Ruma<search_events::v3::Request>)
.iter() .iter()
.skip(skip) .skip(skip)
.filter_map(|result| { .filter_map(|result| {
services() services
.rooms .rooms
.timeline .timeline
.get_pdu_from_id(result) .get_pdu_from_id(result)
.ok()? .ok()?
.filter(|pdu| { .filter(|pdu| {
!pdu.is_redacted() !pdu.is_redacted()
&& services() && services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id) .user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
+37 -21
View File
@@ -1,3 +1,5 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -20,7 +22,7 @@ use serde::Deserialize;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH}; use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{services, utils, utils::hash, Error, Result, Ruma}; use crate::{utils, utils::hash, Error, Result, Ruma};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct Claims { struct Claims {
@@ -55,7 +57,10 @@ pub(crate) async fn get_login_types_route(
/// Note: You can use [`GET /// Note: You can use [`GET
/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see /// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
/// supported login types. /// supported login types.
pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Response> { #[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn login_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<login::v3::Request>,
) -> Result<login::v3::Response> {
// Validate login method // Validate login method
// TODO: Other login methods // TODO: Other login methods
let user_id = match &body.login_info { let user_id = match &body.login_info {
@@ -68,7 +73,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
}) => { }) => {
debug!("Got password login type"); debug!("Got password login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier { let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name()) UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
} else if let Some(user) = user { } else if let Some(user) = user {
UserId::parse(user) UserId::parse(user)
} else { } else {
@@ -77,7 +82,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
} }
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
let hash = services() let hash = services
.users .users
.password_hash(&user_id)? .password_hash(&user_id)?
.ok_or(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."))?; .ok_or(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."))?;
@@ -96,7 +101,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
token, token,
}) => { }) => {
debug!("Got token login type"); debug!("Got token login type");
if let Some(jwt_decoding_key) = services().globals.jwt_decoding_key() { if let Some(jwt_decoding_key) = services.globals.jwt_decoding_key() {
let token = let token =
jsonwebtoken::decode::<Claims>(token, jwt_decoding_key, &jsonwebtoken::Validation::default()) jsonwebtoken::decode::<Claims>(token, jwt_decoding_key, &jsonwebtoken::Validation::default())
.map_err(|e| { .map_err(|e| {
@@ -106,7 +111,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
let username = token.claims.sub.to_lowercase(); let username = token.claims.sub.to_lowercase();
UserId::parse_with_server_name(username, services().globals.server_name()).map_err(|e| { UserId::parse_with_server_name(username, services.globals.server_name()).map_err(|e| {
warn!("Failed to parse username from user logging in: {e}"); warn!("Failed to parse username from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.") Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})? })?
@@ -124,7 +129,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
}) => { }) => {
debug!("Got appservice login type"); debug!("Got appservice login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier { let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name()) UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
} else if let Some(user) = user { } else if let Some(user) = user {
UserId::parse(user) UserId::parse(user)
} else { } else {
@@ -164,22 +169,26 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
// Determine if device_id was provided and exists in the db for this user // Determine if device_id was provided and exists in the db for this user
let device_exists = body.device_id.as_ref().map_or(false, |device_id| { let device_exists = body.device_id.as_ref().map_or(false, |device_id| {
services() services
.users .users
.all_device_ids(&user_id) .all_device_ids(&user_id)
.any(|x| x.as_ref().map_or(false, |v| v == device_id)) .any(|x| x.as_ref().map_or(false, |v| v == device_id))
}); });
if device_exists { if device_exists {
services().users.set_token(&user_id, &device_id, &token)?; services.users.set_token(&user_id, &device_id, &token)?;
} else { } else {
services() services.users.create_device(
.users &user_id,
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?; &device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)?;
} }
// send client well-known if specified so the client knows to reconfigure itself // send client well-known if specified so the client knows to reconfigure itself
let client_discovery_info: Option<DiscoveryInfo> = services() let client_discovery_info: Option<DiscoveryInfo> = services
.globals .globals
.well_known_client() .well_known_client()
.as_ref() .as_ref()
@@ -197,7 +206,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
device_id, device_id,
well_known: client_discovery_info, well_known: client_discovery_info,
expires_in: None, expires_in: None,
home_server: Some(services().globals.server_name().to_owned()), home_server: Some(services.globals.server_name().to_owned()),
refresh_token: None, refresh_token: None,
}) })
} }
@@ -211,14 +220,17 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
/// last seen ts) /// last seen ts)
/// - Forgets to-device events /// - Forgets to-device events
/// - Triggers device list updates /// - Triggers device list updates
pub(crate) async fn logout_route(body: Ruma<logout::v3::Request>) -> Result<logout::v3::Response> { #[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<logout::v3::Request>,
) -> Result<logout::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
services().users.remove_device(sender_user, sender_device)?; services.users.remove_device(sender_user, sender_device)?;
// send device list update for user after logout // send device list update for user after logout
services().users.mark_device_key_update(sender_user)?; services.users.mark_device_key_update(sender_user)?;
Ok(logout::v3::Response::new()) Ok(logout::v3::Response::new())
} }
@@ -236,15 +248,19 @@ pub(crate) async fn logout_route(body: Ruma<logout::v3::Request>) -> Result<logo
/// Note: This is equivalent to calling [`GET /// Note: This is equivalent to calling [`GET
/// /_matrix/client/r0/logout`](fn.logout_route.html) from each device of this /// /_matrix/client/r0/logout`](fn.logout_route.html) from each device of this
/// user. /// user.
pub(crate) async fn logout_all_route(body: Ruma<logout_all::v3::Request>) -> Result<logout_all::v3::Response> { #[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_all_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for device_id in services().users.all_device_ids(sender_user).flatten() { for device_id in services.users.all_device_ids(sender_user).flatten() {
services().users.remove_device(sender_user, &device_id)?; services.users.remove_device(sender_user, &device_id)?;
} }
// send device list update for user after logout // send device list update for user after logout
services().users.mark_device_key_update(sender_user)?; services.users.mark_device_key_update(sender_user)?;
Ok(logout_all::v3::Response::new()) Ok(logout_all::v3::Response::new())
} }
+6 -3
View File
@@ -1,17 +1,20 @@
use std::str::FromStr; use std::str::FromStr;
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{error::ErrorKind, space::get_hierarchy}, api::client::{error::ErrorKind, space::get_hierarchy},
UInt, UInt,
}; };
use crate::{service::rooms::spaces::PaginationToken, services, Error, Result, Ruma}; use crate::{service::rooms::spaces::PaginationToken, Error, Result, Ruma};
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy` /// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy`
/// ///
/// Paginates over the space tree in a depth-first manner to locate child rooms /// Paginates over the space tree in a depth-first manner to locate child rooms
/// of a given space. /// of a given space.
pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> { pub(crate) async fn get_hierarchy_route(
State(services): State<crate::State>, body: Ruma<get_hierarchy::v1::Request>,
) -> Result<get_hierarchy::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let limit = body let limit = body
@@ -39,7 +42,7 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
} }
} }
services() services
.rooms .rooms
.spaces .spaces
.get_client_hierarchy( .get_client_hierarchy(
+31 -26
View File
@@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use conduit::{debug_info, error}; use axum::extract::State;
use conduit::{debug_info, error, pdu::PduBuilder, Error, Result};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -17,11 +18,9 @@ use ruma::{
serde::Raw, serde::Raw,
EventId, RoomId, UserId, EventId, RoomId, UserId,
}; };
use service::Services;
use crate::{ use crate::{Ruma, RumaResponse};
service::{pdu::PduBuilder, server_is_ours},
services, Error, Result, Ruma, RumaResponse,
};
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}` /// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
/// ///
@@ -32,12 +31,13 @@ use crate::{
/// allowed /// allowed
/// - If event is new `canonical_alias`: Rejects if alias is incorrect /// - If event is new `canonical_alias`: Rejects if alias is incorrect
pub(crate) async fn send_state_event_for_key_route( pub(crate) async fn send_state_event_for_key_route(
body: Ruma<send_state_event::v3::Request>, State(services): State<crate::State>, body: Ruma<send_state_event::v3::Request>,
) -> Result<send_state_event::v3::Response> { ) -> Result<send_state_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(send_state_event::v3::Response { Ok(send_state_event::v3::Response {
event_id: send_state_event_for_key_helper( event_id: send_state_event_for_key_helper(
&services,
sender_user, sender_user,
&body.room_id, &body.room_id,
&body.event_type, &body.event_type,
@@ -58,9 +58,11 @@ pub(crate) async fn send_state_event_for_key_route(
/// allowed /// allowed
/// - If event is new `canonical_alias`: Rejects if alias is incorrect /// - If event is new `canonical_alias`: Rejects if alias is incorrect
pub(crate) async fn send_state_event_for_empty_key_route( pub(crate) async fn send_state_event_for_empty_key_route(
body: Ruma<send_state_event::v3::Request>, State(services): State<crate::State>, body: Ruma<send_state_event::v3::Request>,
) -> Result<RumaResponse<send_state_event::v3::Response>> { ) -> Result<RumaResponse<send_state_event::v3::Response>> {
send_state_event_for_key_route(body).await.map(RumaResponse) send_state_event_for_key_route(State(services), body)
.await
.map(RumaResponse)
} }
/// # `GET /_matrix/client/v3/rooms/{roomid}/state` /// # `GET /_matrix/client/v3/rooms/{roomid}/state`
@@ -70,11 +72,11 @@ pub(crate) async fn send_state_event_for_empty_key_route(
/// - If not joined: Only works if current room history visibility is world /// - If not joined: Only works if current room history visibility is world
/// readable /// readable
pub(crate) async fn get_state_events_route( pub(crate) async fn get_state_events_route(
body: Ruma<get_state_events::v3::Request>, State(services): State<crate::State>, body: Ruma<get_state_events::v3::Request>,
) -> Result<get_state_events::v3::Response> { ) -> Result<get_state_events::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_state_events(sender_user, &body.room_id)? .user_can_see_state_events(sender_user, &body.room_id)?
@@ -86,7 +88,7 @@ pub(crate) async fn get_state_events_route(
} }
Ok(get_state_events::v3::Response { Ok(get_state_events::v3::Response {
room_state: services() room_state: services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full(&body.room_id) .room_state_full(&body.room_id)
@@ -106,11 +108,11 @@ pub(crate) async fn get_state_events_route(
/// - If not joined: Only works if current room history visibility is world /// - If not joined: Only works if current room history visibility is world
/// readable /// readable
pub(crate) async fn get_state_events_for_key_route( pub(crate) async fn get_state_events_for_key_route(
body: Ruma<get_state_events_for_key::v3::Request>, State(services): State<crate::State>, body: Ruma<get_state_events_for_key::v3::Request>,
) -> Result<get_state_events_for_key::v3::Response> { ) -> Result<get_state_events_for_key::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_state_events(sender_user, &body.room_id)? .user_can_see_state_events(sender_user, &body.room_id)?
@@ -121,7 +123,7 @@ pub(crate) async fn get_state_events_for_key_route(
)); ));
} }
let event = services() let event = services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&body.room_id, &body.event_type, &body.state_key)? .room_state_get(&body.room_id, &body.event_type, &body.state_key)?
@@ -161,17 +163,20 @@ pub(crate) async fn get_state_events_for_key_route(
/// - If not joined: Only works if current room history visibility is world /// - If not joined: Only works if current room history visibility is world
/// readable /// readable
pub(crate) async fn get_state_events_for_empty_key_route( pub(crate) async fn get_state_events_for_empty_key_route(
body: Ruma<get_state_events_for_key::v3::Request>, State(services): State<crate::State>, body: Ruma<get_state_events_for_key::v3::Request>,
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> { ) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
get_state_events_for_key_route(body).await.map(RumaResponse) get_state_events_for_key_route(State(services), body)
.await
.map(RumaResponse)
} }
async fn send_state_event_for_key_helper( async fn send_state_event_for_key_helper(
sender: &UserId, room_id: &RoomId, event_type: &StateEventType, json: &Raw<AnyStateEventContent>, state_key: String, services: &Services, sender: &UserId, room_id: &RoomId, event_type: &StateEventType,
json: &Raw<AnyStateEventContent>, state_key: String,
) -> Result<Arc<EventId>> { ) -> Result<Arc<EventId>> {
allowed_to_send_state_event(room_id, event_type, json).await?; allowed_to_send_state_event(services, room_id, event_type, json).await?;
let state_lock = services().rooms.state.mutex.lock(room_id).await; let state_lock = services.rooms.state.mutex.lock(room_id).await;
let event_id = services() let event_id = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
@@ -192,18 +197,18 @@ async fn send_state_event_for_key_helper(
} }
async fn allowed_to_send_state_event( async fn allowed_to_send_state_event(
room_id: &RoomId, event_type: &StateEventType, json: &Raw<AnyStateEventContent>, services: &Services, room_id: &RoomId, event_type: &StateEventType, json: &Raw<AnyStateEventContent>,
) -> Result<()> { ) -> Result<()> {
match event_type { match event_type {
// Forbid m.room.encryption if encryption is disabled // Forbid m.room.encryption if encryption is disabled
StateEventType::RoomEncryption => { StateEventType::RoomEncryption => {
if !services().globals.allow_encryption() { if !services.globals.allow_encryption() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
} }
}, },
// admin room is a sensitive room, it should not ever be made public // admin room is a sensitive room, it should not ever be made public
StateEventType::RoomJoinRules => { StateEventType::RoomJoinRules => {
if let Some(admin_room_id) = service::admin::Service::get_admin_room()? { if let Some(admin_room_id) = services.admin.get_admin_room()? {
if admin_room_id == room_id { if admin_room_id == room_id {
if let Ok(join_rule) = serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get()) { if let Ok(join_rule) = serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get()) {
if join_rule.join_rule == JoinRule::Public { if join_rule.join_rule == JoinRule::Public {
@@ -218,7 +223,7 @@ async fn allowed_to_send_state_event(
}, },
// admin room is a sensitive room, it should not ever be made world readable // admin room is a sensitive room, it should not ever be made world readable
StateEventType::RoomHistoryVisibility => { StateEventType::RoomHistoryVisibility => {
if let Some(admin_room_id) = service::admin::Service::get_admin_room()? { if let Some(admin_room_id) = services.admin.get_admin_room()? {
if admin_room_id == room_id { if admin_room_id == room_id {
if let Ok(visibility_content) = if let Ok(visibility_content) =
serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get()) serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get())
@@ -243,8 +248,8 @@ async fn allowed_to_send_state_event(
} }
for alias in aliases { for alias in aliases {
if !server_is_ours(alias.server_name()) if !services.globals.server_is_ours(alias.server_name())
|| services() || services
.rooms .rooms
.alias .alias
.resolve_local_alias(&alias)? .resolve_local_alias(&alias)?
+154 -142
View File
@@ -5,6 +5,7 @@ use std::{
time::Duration, time::Duration,
}; };
use axum::extract::State;
use conduit::{ use conduit::{
error, error,
utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated}, utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
@@ -17,7 +18,7 @@ use ruma::{
self, self,
v3::{ v3::{
Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom, LeftRoom, Presence, Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom, LeftRoom, Presence,
RoomAccountData, RoomSummary, Rooms, State, Timeline, ToDevice, RoomAccountData, RoomSummary, Rooms, State as RoomState, Timeline, ToDevice,
}, },
v4::SlidingOp, v4::SlidingOp,
DeviceLists, UnreadNotificationsCount, DeviceLists, UnreadNotificationsCount,
@@ -34,7 +35,10 @@ use ruma::{
}; };
use tracing::{Instrument as _, Span}; use tracing::{Instrument as _, Span};
use crate::{service::pdu::EventHash, services, utils, Error, PduEvent, Result, Ruma, RumaResponse}; use crate::{
service::{pdu::EventHash, Services},
utils, Error, PduEvent, Result, Ruma, RumaResponse,
};
/// # `GET /_matrix/client/r0/sync` /// # `GET /_matrix/client/r0/sync`
/// ///
@@ -72,23 +76,23 @@ use crate::{service::pdu::EventHash, services, utils, Error, PduEvent, Result, R
/// - If the user left after `since`: `prev_batch` token, empty state (TODO: /// - If the user left after `since`: `prev_batch` token, empty state (TODO:
/// subset of the state at the point of the leave) /// subset of the state at the point of the leave)
pub(crate) async fn sync_events_route( pub(crate) async fn sync_events_route(
body: Ruma<sync_events::v3::Request>, State(services): State<crate::State>, body: Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response, RumaResponse<UiaaResponse>> { ) -> Result<sync_events::v3::Response, RumaResponse<UiaaResponse>> {
let sender_user = body.sender_user.expect("user is authenticated"); let sender_user = body.sender_user.expect("user is authenticated");
let sender_device = body.sender_device.expect("user is authenticated"); let sender_device = body.sender_device.expect("user is authenticated");
let body = body.body; let body = body.body;
// Presence update // Presence update
if services().globals.allow_local_presence() { if services.globals.allow_local_presence() {
services() services
.presence .presence
.ping_presence(&sender_user, &body.set_presence)?; .ping_presence(&sender_user, &body.set_presence)?;
} }
// Setup watchers, so if there's no response, we can wait for them // Setup watchers, so if there's no response, we can wait for them
let watcher = services().globals.watch(&sender_user, &sender_device); let watcher = services.globals.watch(&sender_user, &sender_device);
let next_batch = services().globals.current_count()?; let next_batch = services.globals.current_count()?;
let next_batchcount = PduCount::Normal(next_batch); let next_batchcount = PduCount::Normal(next_batch);
let next_batch_string = next_batch.to_string(); let next_batch_string = next_batch.to_string();
@@ -96,17 +100,19 @@ pub(crate) async fn sync_events_route(
let filter = match body.filter { let filter = match body.filter {
None => FilterDefinition::default(), None => FilterDefinition::default(),
Some(Filter::FilterDefinition(filter)) => filter, Some(Filter::FilterDefinition(filter)) => filter,
Some(Filter::FilterId(filter_id)) => services() Some(Filter::FilterId(filter_id)) => services
.users .users
.get_filter(&sender_user, &filter_id)? .get_filter(&sender_user, &filter_id)?
.unwrap_or_default(), .unwrap_or_default(),
}; };
// some clients, at least element, seem to require knowledge of redundant
// members for "inline" profiles on the timeline to work properly
let (lazy_load_enabled, lazy_load_send_redundant) = match filter.room.state.lazy_load_options { let (lazy_load_enabled, lazy_load_send_redundant) = match filter.room.state.lazy_load_options {
LazyLoadOptions::Enabled { LazyLoadOptions::Enabled {
include_redundant_members: redundant, include_redundant_members,
} => (true, redundant), } => (true, include_redundant_members),
LazyLoadOptions::Disabled => (false, false), LazyLoadOptions::Disabled => (false, cfg!(feature = "element_hacks")),
}; };
let full_state = body.full_state; let full_state = body.full_state;
@@ -126,28 +132,29 @@ pub(crate) async fn sync_events_route(
// Look for device list updates of this account // Look for device list updates of this account
device_list_updates.extend( device_list_updates.extend(
services() services
.users .users
.keys_changed(sender_user.as_ref(), since, None) .keys_changed(sender_user.as_ref(), since, None)
.filter_map(Result::ok), .filter_map(Result::ok),
); );
if services().globals.allow_local_presence() { if services.globals.allow_local_presence() {
process_presence_updates(&mut presence_updates, since, &sender_user).await?; process_presence_updates(&services, &mut presence_updates, since, &sender_user).await?;
} }
let all_joined_rooms = services() let all_joined_rooms = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&sender_user) .rooms_joined(&sender_user)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Coalesce database writes for the remainder of this scope. // Coalesce database writes for the remainder of this scope.
let _cork = services().db.cork_and_flush(); let _cork = services.db.cork_and_flush();
for room_id in all_joined_rooms { for room_id in all_joined_rooms {
let room_id = room_id?; let room_id = room_id?;
if let Ok(joined_room) = load_joined_room( if let Ok(joined_room) = load_joined_room(
&services,
&sender_user, &sender_user,
&sender_device, &sender_device,
&room_id, &room_id,
@@ -170,13 +177,14 @@ pub(crate) async fn sync_events_route(
} }
let mut left_rooms = BTreeMap::new(); let mut left_rooms = BTreeMap::new();
let all_left_rooms: Vec<_> = services() let all_left_rooms: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.rooms_left(&sender_user) .rooms_left(&sender_user)
.collect(); .collect();
for result in all_left_rooms { for result in all_left_rooms {
handle_left_room( handle_left_room(
&services,
since, since,
&result?.0, &result?.0,
&sender_user, &sender_user,
@@ -190,7 +198,7 @@ pub(crate) async fn sync_events_route(
} }
let mut invited_rooms = BTreeMap::new(); let mut invited_rooms = BTreeMap::new();
let all_invited_rooms: Vec<_> = services() let all_invited_rooms: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.rooms_invited(&sender_user) .rooms_invited(&sender_user)
@@ -199,10 +207,10 @@ pub(crate) async fn sync_events_route(
let (room_id, invite_state_events) = result?; let (room_id, invite_state_events) = result?;
// Get and drop the lock to wait for remaining operations to finish // Get and drop the lock to wait for remaining operations to finish
let insert_lock = services().rooms.timeline.mutex_insert.lock(&room_id).await; let insert_lock = services.rooms.timeline.mutex_insert.lock(&room_id).await;
drop(insert_lock); drop(insert_lock);
let invite_count = services() let invite_count = services
.rooms .rooms
.state_cache .state_cache
.get_invite_count(&room_id, &sender_user)?; .get_invite_count(&room_id, &sender_user)?;
@@ -223,14 +231,14 @@ pub(crate) async fn sync_events_route(
} }
for user_id in left_encrypted_users { for user_id in left_encrypted_users {
let dont_share_encrypted_room = services() let dont_share_encrypted_room = services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])? .get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(Result::ok) .filter_map(Result::ok)
.filter_map(|other_room_id| { .filter_map(|other_room_id| {
Some( Some(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&other_room_id, &StateEventType::RoomEncryption, "") .room_state_get(&other_room_id, &StateEventType::RoomEncryption, "")
@@ -247,7 +255,7 @@ pub(crate) async fn sync_events_route(
} }
// Remove all to-device events the device received *last time* // Remove all to-device events the device received *last time*
services() services
.users .users
.remove_to_device_events(&sender_user, &sender_device, since)?; .remove_to_device_events(&sender_user, &sender_device, since)?;
@@ -266,7 +274,7 @@ pub(crate) async fn sync_events_route(
.collect(), .collect(),
}, },
account_data: GlobalAccountData { account_data: GlobalAccountData {
events: services() events: services
.account_data .account_data
.changes_since(None, &sender_user, since)? .changes_since(None, &sender_user, since)?
.into_iter() .into_iter()
@@ -281,11 +289,11 @@ pub(crate) async fn sync_events_route(
changed: device_list_updates.into_iter().collect(), changed: device_list_updates.into_iter().collect(),
left: device_list_left.into_iter().collect(), left: device_list_left.into_iter().collect(),
}, },
device_one_time_keys_count: services() device_one_time_keys_count: services
.users .users
.count_one_time_keys(&sender_user, &sender_device)?, .count_one_time_keys(&sender_user, &sender_device)?,
to_device: ToDevice { to_device: ToDevice {
events: services() events: services
.users .users
.get_to_device_events(&sender_user, &sender_device)?, .get_to_device_events(&sender_user, &sender_device)?,
}, },
@@ -311,16 +319,18 @@ pub(crate) async fn sync_events_route(
Ok(response) Ok(response)
} }
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip_all, fields(user_id = %sender_user, room_id = %room_id), name = "left_room")] #[tracing::instrument(skip_all, fields(user_id = %sender_user, room_id = %room_id), name = "left_room")]
async fn handle_left_room( async fn handle_left_room(
since: u64, room_id: &RoomId, sender_user: &UserId, left_rooms: &mut BTreeMap<ruma::OwnedRoomId, LeftRoom>, services: &Services, since: u64, room_id: &RoomId, sender_user: &UserId,
next_batch_string: &str, full_state: bool, lazy_load_enabled: bool, left_rooms: &mut BTreeMap<ruma::OwnedRoomId, LeftRoom>, next_batch_string: &str, full_state: bool,
lazy_load_enabled: bool,
) -> Result<()> { ) -> Result<()> {
// Get and drop the lock to wait for remaining operations to finish // Get and drop the lock to wait for remaining operations to finish
let insert_lock = services().rooms.timeline.mutex_insert.lock(room_id).await; let insert_lock = services.rooms.timeline.mutex_insert.lock(room_id).await;
drop(insert_lock); drop(insert_lock);
let left_count = services() let left_count = services
.rooms .rooms
.state_cache .state_cache
.get_left_count(room_id, sender_user)?; .get_left_count(room_id, sender_user)?;
@@ -330,11 +340,11 @@ async fn handle_left_room(
return Ok(()); return Ok(());
} }
if !services().rooms.metadata.exists(room_id)? { if !services.rooms.metadata.exists(room_id)? {
// This is just a rejected invite, not a room we know // This is just a rejected invite, not a room we know
// Insert a leave event anyways // Insert a leave event anyways
let event = PduEvent { let event = PduEvent {
event_id: EventId::new(services().globals.server_name()).into(), event_id: EventId::new(services.globals.server_name()).into(),
sender: sender_user.to_owned(), sender: sender_user.to_owned(),
origin: None, origin: None,
origin_server_ts: utils::millis_since_unix_epoch() origin_server_ts: utils::millis_since_unix_epoch()
@@ -367,7 +377,7 @@ async fn handle_left_room(
prev_batch: Some(next_batch_string.to_owned()), prev_batch: Some(next_batch_string.to_owned()),
events: Vec::new(), events: Vec::new(),
}, },
state: State { state: RoomState {
events: vec![event.to_sync_state_event()], events: vec![event.to_sync_state_event()],
}, },
}, },
@@ -377,27 +387,27 @@ async fn handle_left_room(
let mut left_state_events = Vec::new(); let mut left_state_events = Vec::new();
let since_shortstatehash = services() let since_shortstatehash = services
.rooms .rooms
.user .user
.get_token_shortstatehash(room_id, since)?; .get_token_shortstatehash(room_id, since)?;
let since_state_ids = match since_shortstatehash { let since_state_ids = match since_shortstatehash {
Some(s) => services().rooms.state_accessor.state_full_ids(s).await?, Some(s) => services.rooms.state_accessor.state_full_ids(s).await?,
None => HashMap::new(), None => HashMap::new(),
}; };
let Some(left_event_id) = services().rooms.state_accessor.room_state_get_id( let Some(left_event_id) =
room_id, services
&StateEventType::RoomMember, .rooms
sender_user.as_str(), .state_accessor
)? .room_state_get_id(room_id, &StateEventType::RoomMember, sender_user.as_str())?
else { else {
error!("Left room but no left state event"); error!("Left room but no left state event");
return Ok(()); return Ok(());
}; };
let Some(left_shortstatehash) = services() let Some(left_shortstatehash) = services
.rooms .rooms
.state_accessor .state_accessor
.pdu_shortstatehash(&left_event_id)? .pdu_shortstatehash(&left_event_id)?
@@ -406,13 +416,13 @@ async fn handle_left_room(
return Ok(()); return Ok(());
}; };
let mut left_state_ids = services() let mut left_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(left_shortstatehash) .state_full_ids(left_shortstatehash)
.await?; .await?;
let leave_shortstatekey = services() let leave_shortstatekey = services
.rooms .rooms
.short .short
.get_or_create_shortstatekey(&StateEventType::RoomMember, sender_user.as_str())?; .get_or_create_shortstatekey(&StateEventType::RoomMember, sender_user.as_str())?;
@@ -422,7 +432,7 @@ async fn handle_left_room(
let mut i: u8 = 0; let mut i: u8 = 0;
for (key, id) in left_state_ids { for (key, id) in left_state_ids {
if full_state || since_state_ids.get(&key) != Some(&id) { if full_state || since_state_ids.get(&key) != Some(&id) {
let (event_type, state_key) = services().rooms.short.get_statekey_from_short(key)?; let (event_type, state_key) = services.rooms.short.get_statekey_from_short(key)?;
if !lazy_load_enabled if !lazy_load_enabled
|| event_type != StateEventType::RoomMember || event_type != StateEventType::RoomMember
@@ -430,7 +440,7 @@ async fn handle_left_room(
// TODO: Delete the following line when this is resolved: https://github.com/vector-im/element-web/issues/22565 // TODO: Delete the following line when this is resolved: https://github.com/vector-im/element-web/issues/22565
|| (cfg!(feature = "element_hacks") && *sender_user == state_key) || (cfg!(feature = "element_hacks") && *sender_user == state_key)
{ {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
@@ -456,7 +466,7 @@ async fn handle_left_room(
prev_batch: Some(next_batch_string.to_owned()), prev_batch: Some(next_batch_string.to_owned()),
events: Vec::new(), events: Vec::new(),
}, },
state: State { state: RoomState {
events: left_state_events, events: left_state_events,
}, },
}, },
@@ -465,13 +475,11 @@ async fn handle_left_room(
} }
async fn process_presence_updates( async fn process_presence_updates(
presence_updates: &mut HashMap<OwnedUserId, PresenceEvent>, since: u64, syncing_user: &UserId, services: &Services, presence_updates: &mut HashMap<OwnedUserId, PresenceEvent>, since: u64, syncing_user: &UserId,
) -> Result<()> { ) -> Result<()> {
use crate::service::presence::Presence;
// Take presence updates // Take presence updates
for (user_id, _, presence_bytes) in services().presence.presence_since(since) { for (user_id, _, presence_bytes) in services.presence.presence_since(since) {
if !services() if !services
.rooms .rooms
.state_cache .state_cache
.user_sees_user(syncing_user, &user_id)? .user_sees_user(syncing_user, &user_id)?
@@ -479,7 +487,9 @@ async fn process_presence_updates(
continue; continue;
} }
let presence_event = Presence::from_json_bytes_to_event(&presence_bytes, &user_id)?; let presence_event = services
.presence
.from_json_bytes_to_event(&presence_bytes, &user_id)?;
match presence_updates.entry(user_id) { match presence_updates.entry(user_id) {
Entry::Vacant(slot) => { Entry::Vacant(slot) => {
slot.insert(presence_event); slot.insert(presence_event);
@@ -513,19 +523,20 @@ async fn process_presence_updates(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn load_joined_room( async fn load_joined_room(
sender_user: &UserId, sender_device: &DeviceId, room_id: &RoomId, since: u64, sincecount: PduCount, services: &Services, sender_user: &UserId, sender_device: &DeviceId, room_id: &RoomId, since: u64,
next_batch: u64, next_batchcount: PduCount, lazy_load_enabled: bool, lazy_load_send_redundant: bool, sincecount: PduCount, next_batch: u64, next_batchcount: PduCount, lazy_load_enabled: bool,
full_state: bool, device_list_updates: &mut HashSet<OwnedUserId>, left_encrypted_users: &mut HashSet<OwnedUserId>, lazy_load_send_redundant: bool, full_state: bool, device_list_updates: &mut HashSet<OwnedUserId>,
left_encrypted_users: &mut HashSet<OwnedUserId>,
) -> Result<JoinedRoom> { ) -> Result<JoinedRoom> {
// Get and drop the lock to wait for remaining operations to finish // Get and drop the lock to wait for remaining operations to finish
// This will make sure the we have all events until next_batch // This will make sure the we have all events until next_batch
let insert_lock = services().rooms.timeline.mutex_insert.lock(room_id).await; let insert_lock = services.rooms.timeline.mutex_insert.lock(room_id).await;
drop(insert_lock); drop(insert_lock);
let (timeline_pdus, limited) = load_timeline(sender_user, room_id, sincecount, 10)?; let (timeline_pdus, limited) = load_timeline(services, sender_user, room_id, sincecount, 10)?;
let send_notification_counts = !timeline_pdus.is_empty() let send_notification_counts = !timeline_pdus.is_empty()
|| services() || services
.rooms .rooms
.user .user
.last_notification_read(sender_user, room_id)? .last_notification_read(sender_user, room_id)?
@@ -536,7 +547,7 @@ async fn load_joined_room(
timeline_users.insert(event.sender.as_str().to_owned()); timeline_users.insert(event.sender.as_str().to_owned());
} }
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, room_id, sincecount) .lazy_load_confirm_delivery(sender_user, sender_device, room_id, sincecount)
@@ -544,11 +555,11 @@ async fn load_joined_room(
// Database queries: // Database queries:
let Some(current_shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else { let Some(current_shortstatehash) = services.rooms.state.get_room_shortstatehash(room_id)? else {
return Err!(Database(error!("Room {room_id} has no state"))); return Err!(Database(error!("Room {room_id} has no state")));
}; };
let since_shortstatehash = services() let since_shortstatehash = services
.rooms .rooms
.user .user
.get_token_shortstatehash(room_id, since)?; .get_token_shortstatehash(room_id, since)?;
@@ -560,12 +571,12 @@ async fn load_joined_room(
} else { } else {
// Calculates joined_member_count, invited_member_count and heroes // Calculates joined_member_count, invited_member_count and heroes
let calculate_counts = || { let calculate_counts = || {
let joined_member_count = services() let joined_member_count = services
.rooms .rooms
.state_cache .state_cache
.room_joined_count(room_id)? .room_joined_count(room_id)?
.unwrap_or(0); .unwrap_or(0);
let invited_member_count = services() let invited_member_count = services
.rooms .rooms
.state_cache .state_cache
.room_invited_count(room_id)? .room_invited_count(room_id)?
@@ -578,7 +589,7 @@ async fn load_joined_room(
// Go through all PDUs and for each member event, check if the user is still // Go through all PDUs and for each member event, check if the user is still
// joined or invited until we have 5 or we reach the end // joined or invited until we have 5 or we reach the end
for hero in services() for hero in services
.rooms .rooms
.timeline .timeline
.all_pdus(sender_user, room_id)? .all_pdus(sender_user, room_id)?
@@ -594,8 +605,8 @@ async fn load_joined_room(
// The membership was and still is invite or join // The membership was and still is invite or join
if matches!(content.membership, MembershipState::Join | MembershipState::Invite) if matches!(content.membership, MembershipState::Join | MembershipState::Invite)
&& (services().rooms.state_cache.is_joined(&user_id, room_id)? && (services.rooms.state_cache.is_joined(&user_id, room_id)?
|| services().rooms.state_cache.is_invited(&user_id, room_id)?) || services.rooms.state_cache.is_invited(&user_id, room_id)?)
{ {
Ok::<_, Error>(Some(user_id)) Ok::<_, Error>(Some(user_id))
} else { } else {
@@ -622,7 +633,7 @@ async fn load_joined_room(
let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash
.and_then(|shortstatehash| { .and_then(|shortstatehash| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.state_get(shortstatehash, &StateEventType::RoomMember, sender_user.as_str()) .state_get(shortstatehash, &StateEventType::RoomMember, sender_user.as_str())
@@ -643,7 +654,7 @@ async fn load_joined_room(
let (joined_member_count, invited_member_count, heroes) = calculate_counts()?; let (joined_member_count, invited_member_count, heroes) = calculate_counts()?;
let current_state_ids = services() let current_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(current_shortstatehash) .state_full_ids(current_shortstatehash)
@@ -654,13 +665,13 @@ async fn load_joined_room(
let mut i: u8 = 0; let mut i: u8 = 0;
for (shortstatekey, id) in current_state_ids { for (shortstatekey, id) in current_state_ids {
let (event_type, state_key) = services() let (event_type, state_key) = services
.rooms .rooms
.short .short
.get_statekey_from_short(shortstatekey)?; .get_statekey_from_short(shortstatekey)?;
if event_type != StateEventType::RoomMember { if event_type != StateEventType::RoomMember {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
@@ -676,7 +687,7 @@ async fn load_joined_room(
// TODO: Delete the following line when this is resolved: https://github.com/vector-im/element-web/issues/22565 // TODO: Delete the following line when this is resolved: https://github.com/vector-im/element-web/issues/22565
|| (cfg!(feature = "element_hacks") && *sender_user == state_key) || (cfg!(feature = "element_hacks") && *sender_user == state_key)
{ {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
@@ -695,14 +706,14 @@ async fn load_joined_room(
} }
// Reset lazy loading because this is an initial sync // Reset lazy loading because this is an initial sync
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_reset(sender_user, sender_device, room_id)?; .lazy_load_reset(sender_user, sender_device, room_id)?;
// The state_events above should contain all timeline_users, let's mark them as // The state_events above should contain all timeline_users, let's mark them as
// lazy loaded. // lazy loaded.
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount) .lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount)
@@ -716,12 +727,12 @@ async fn load_joined_room(
let mut delta_state_events = Vec::new(); let mut delta_state_events = Vec::new();
if since_shortstatehash != current_shortstatehash { if since_shortstatehash != current_shortstatehash {
let current_state_ids = services() let current_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(current_shortstatehash) .state_full_ids(current_shortstatehash)
.await?; .await?;
let since_state_ids = services() let since_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(since_shortstatehash) .state_full_ids(since_shortstatehash)
@@ -729,7 +740,7 @@ async fn load_joined_room(
for (key, id) in current_state_ids { for (key, id) in current_state_ids {
if full_state || since_state_ids.get(&key) != Some(&id) { if full_state || since_state_ids.get(&key) != Some(&id) {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
@@ -740,13 +751,13 @@ async fn load_joined_room(
} }
} }
let encrypted_room = services() let encrypted_room = services
.rooms .rooms
.state_accessor .state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")? .state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")?
.is_some(); .is_some();
let since_encryption = services().rooms.state_accessor.state_get( let since_encryption = services.rooms.state_accessor.state_get(
since_shortstatehash, since_shortstatehash,
&StateEventType::RoomEncryption, &StateEventType::RoomEncryption,
"", "",
@@ -781,7 +792,7 @@ async fn load_joined_room(
match new_membership { match new_membership {
MembershipState::Join => { MembershipState::Join => {
// A new user joined an encrypted room // A new user joined an encrypted room
if !share_encrypted_room(sender_user, &user_id, room_id)? { if !share_encrypted_room(services, sender_user, &user_id, room_id)? {
device_list_updates.insert(user_id); device_list_updates.insert(user_id);
} }
}, },
@@ -798,7 +809,7 @@ async fn load_joined_room(
if joined_since_last_sync && encrypted_room || new_encrypted_room { if joined_since_last_sync && encrypted_room || new_encrypted_room {
// If the user is in a new encrypted room, give them all joined users // If the user is in a new encrypted room, give them all joined users
device_list_updates.extend( device_list_updates.extend(
services() services
.rooms .rooms
.state_cache .state_cache
.room_members(room_id) .room_members(room_id)
@@ -810,7 +821,7 @@ async fn load_joined_room(
.filter(|user_id| { .filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target // Only send keys if the sender doesn't share an encrypted room with the target
// already // already
!share_encrypted_room(sender_user, user_id, room_id).unwrap_or(false) !share_encrypted_room(services, sender_user, user_id, room_id).unwrap_or(false)
}), }),
); );
} }
@@ -848,14 +859,14 @@ async fn load_joined_room(
continue; continue;
} }
if !services().rooms.lazy_loading.lazy_load_was_sent_before( if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user, sender_user,
sender_device, sender_device,
room_id, room_id,
&event.sender, &event.sender,
)? || lazy_load_send_redundant )? || lazy_load_send_redundant
{ {
if let Some(member_event) = services().rooms.state_accessor.room_state_get( if let Some(member_event) = services.rooms.state_accessor.room_state_get(
room_id, room_id,
&StateEventType::RoomMember, &StateEventType::RoomMember,
event.sender.as_str(), event.sender.as_str(),
@@ -866,7 +877,7 @@ async fn load_joined_room(
} }
} }
services() services
.rooms .rooms
.lazy_loading .lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount) .lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount)
@@ -884,7 +895,7 @@ async fn load_joined_room(
// Look for device list updates in this room // Look for device list updates in this room
device_list_updates.extend( device_list_updates.extend(
services() services
.users .users
.keys_changed(room_id.as_ref(), since, None) .keys_changed(room_id.as_ref(), since, None)
.filter_map(Result::ok), .filter_map(Result::ok),
@@ -892,7 +903,7 @@ async fn load_joined_room(
let notification_count = if send_notification_counts { let notification_count = if send_notification_counts {
Some( Some(
services() services
.rooms .rooms
.user .user
.notification_count(sender_user, room_id)? .notification_count(sender_user, room_id)?
@@ -905,7 +916,7 @@ async fn load_joined_room(
let highlight_count = if send_notification_counts { let highlight_count = if send_notification_counts {
Some( Some(
services() services
.rooms .rooms
.user .user
.highlight_count(sender_user, room_id)? .highlight_count(sender_user, room_id)?
@@ -933,7 +944,7 @@ async fn load_joined_room(
.map(|(_, pdu)| pdu.to_sync_room_event()) .map(|(_, pdu)| pdu.to_sync_room_event())
.collect(); .collect();
let mut edus: Vec<_> = services() let mut edus: Vec<_> = services
.rooms .rooms
.read_receipt .read_receipt
.readreceipts_since(room_id, since) .readreceipts_since(room_id, since)
@@ -941,10 +952,10 @@ async fn load_joined_room(
.map(|(_, _, v)| v) .map(|(_, _, v)| v)
.collect(); .collect();
if services().rooms.typing.last_typing_update(room_id).await? > since { if services.rooms.typing.last_typing_update(room_id).await? > since {
edus.push( edus.push(
serde_json::from_str( serde_json::from_str(
&serde_json::to_string(&services().rooms.typing.typings_all(room_id).await?) &serde_json::to_string(&services.rooms.typing.typings_all(room_id).await?)
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
) )
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
@@ -953,14 +964,14 @@ async fn load_joined_room(
// Save the state after this sync so we can send the correct state diff next // Save the state after this sync so we can send the correct state diff next
// sync // sync
services() services
.rooms .rooms
.user .user
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash)?; .associate_token_shortstatehash(room_id, next_batch, current_shortstatehash)?;
Ok(JoinedRoom { Ok(JoinedRoom {
account_data: RoomAccountData { account_data: RoomAccountData {
events: services() events: services
.account_data .account_data
.changes_since(Some(room_id), sender_user, since)? .changes_since(Some(room_id), sender_user, since)?
.into_iter() .into_iter()
@@ -985,7 +996,7 @@ async fn load_joined_room(
prev_batch, prev_batch,
events: room_events, events: room_events,
}, },
state: State { state: RoomState {
events: state_events events: state_events
.iter() .iter()
.map(|pdu| pdu.to_sync_state_event()) .map(|pdu| pdu.to_sync_state_event())
@@ -999,16 +1010,16 @@ async fn load_joined_room(
} }
fn load_timeline( fn load_timeline(
sender_user: &UserId, room_id: &RoomId, roomsincecount: PduCount, limit: u64, services: &Services, sender_user: &UserId, room_id: &RoomId, roomsincecount: PduCount, limit: u64,
) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> { ) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> {
let timeline_pdus; let timeline_pdus;
let limited = if services() let limited = if services
.rooms .rooms
.timeline .timeline
.last_timeline_count(sender_user, room_id)? .last_timeline_count(sender_user, room_id)?
> roomsincecount > roomsincecount
{ {
let mut non_timeline_pdus = services() let mut non_timeline_pdus = services
.rooms .rooms
.timeline .timeline
.pdus_until(sender_user, room_id, PduCount::max())? .pdus_until(sender_user, room_id, PduCount::max())?
@@ -1040,8 +1051,10 @@ fn load_timeline(
Ok((timeline_pdus, limited)) Ok((timeline_pdus, limited))
} }
fn share_encrypted_room(sender_user: &UserId, user_id: &UserId, ignore_room: &RoomId) -> Result<bool> { fn share_encrypted_room(
Ok(services() services: &Services, sender_user: &UserId, user_id: &UserId, ignore_room: &RoomId,
) -> Result<bool> {
Ok(services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.to_owned(), user_id.to_owned()])? .get_shared_rooms(vec![sender_user.to_owned(), user_id.to_owned()])?
@@ -1049,7 +1062,7 @@ fn share_encrypted_room(sender_user: &UserId, user_id: &UserId, ignore_room: &Ro
.filter(|room_id| room_id != ignore_room) .filter(|room_id| room_id != ignore_room)
.filter_map(|other_room_id| { .filter_map(|other_room_id| {
Some( Some(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&other_room_id, &StateEventType::RoomEncryption, "") .room_state_get(&other_room_id, &StateEventType::RoomEncryption, "")
@@ -1064,15 +1077,15 @@ fn share_encrypted_room(sender_user: &UserId, user_id: &UserId, ignore_room: &Ro
/// ///
/// Sliding Sync endpoint (future endpoint: `/_matrix/client/v4/sync`) /// Sliding Sync endpoint (future endpoint: `/_matrix/client/v4/sync`)
pub(crate) async fn sync_events_v4_route( pub(crate) async fn sync_events_v4_route(
body: Ruma<sync_events::v4::Request>, State(services): State<crate::State>, body: Ruma<sync_events::v4::Request>,
) -> Result<sync_events::v4::Response, RumaResponse<UiaaResponse>> { ) -> Result<sync_events::v4::Response, RumaResponse<UiaaResponse>> {
let sender_user = body.sender_user.expect("user is authenticated"); let sender_user = body.sender_user.expect("user is authenticated");
let sender_device = body.sender_device.expect("user is authenticated"); let sender_device = body.sender_device.expect("user is authenticated");
let mut body = body.body; let mut body = body.body;
// Setup watchers, so if there's no response, we can wait for them // Setup watchers, so if there's no response, we can wait for them
let watcher = services().globals.watch(&sender_user, &sender_device); let watcher = services.globals.watch(&sender_user, &sender_device);
let next_batch = services().globals.next_count()?; let next_batch = services.globals.next_count()?;
let globalsince = body let globalsince = body
.pos .pos
@@ -1082,21 +1095,19 @@ pub(crate) async fn sync_events_v4_route(
if globalsince == 0 { if globalsince == 0 {
if let Some(conn_id) = &body.conn_id { if let Some(conn_id) = &body.conn_id {
services().users.forget_sync_request_connection( services
sender_user.clone(), .users
sender_device.clone(), .forget_sync_request_connection(sender_user.clone(), sender_device.clone(), conn_id.clone());
conn_id.clone(),
);
} }
} }
// Get sticky parameters from cache // Get sticky parameters from cache
let known_rooms = let known_rooms =
services() services
.users .users
.update_sync_request_with_cache(sender_user.clone(), sender_device.clone(), &mut body); .update_sync_request_with_cache(sender_user.clone(), sender_device.clone(), &mut body);
let all_joined_rooms = services() let all_joined_rooms = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&sender_user) .rooms_joined(&sender_user)
@@ -1104,7 +1115,7 @@ pub(crate) async fn sync_events_v4_route(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if body.extensions.to_device.enabled.unwrap_or(false) { if body.extensions.to_device.enabled.unwrap_or(false) {
services() services
.users .users
.remove_to_device_events(&sender_user, &sender_device, globalsince)?; .remove_to_device_events(&sender_user, &sender_device, globalsince)?;
} }
@@ -1116,26 +1127,26 @@ pub(crate) async fn sync_events_v4_route(
if body.extensions.e2ee.enabled.unwrap_or(false) { if body.extensions.e2ee.enabled.unwrap_or(false) {
// Look for device list updates of this account // Look for device list updates of this account
device_list_changes.extend( device_list_changes.extend(
services() services
.users .users
.keys_changed(sender_user.as_ref(), globalsince, None) .keys_changed(sender_user.as_ref(), globalsince, None)
.filter_map(Result::ok), .filter_map(Result::ok),
); );
for room_id in &all_joined_rooms { for room_id in &all_joined_rooms {
let Some(current_shortstatehash) = services().rooms.state.get_room_shortstatehash(room_id)? else { let Some(current_shortstatehash) = services.rooms.state.get_room_shortstatehash(room_id)? else {
error!("Room {} has no state", room_id); error!("Room {} has no state", room_id);
continue; continue;
}; };
let since_shortstatehash = services() let since_shortstatehash = services
.rooms .rooms
.user .user
.get_token_shortstatehash(room_id, globalsince)?; .get_token_shortstatehash(room_id, globalsince)?;
let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash
.and_then(|shortstatehash| { .and_then(|shortstatehash| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.state_get(shortstatehash, &StateEventType::RoomMember, sender_user.as_str()) .state_get(shortstatehash, &StateEventType::RoomMember, sender_user.as_str())
@@ -1148,7 +1159,7 @@ pub(crate) async fn sync_events_v4_route(
.ok() .ok()
}); });
let encrypted_room = services() let encrypted_room = services
.rooms .rooms
.state_accessor .state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")? .state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")?
@@ -1160,7 +1171,7 @@ pub(crate) async fn sync_events_v4_route(
continue; continue;
} }
let since_encryption = services().rooms.state_accessor.state_get( let since_encryption = services.rooms.state_accessor.state_get(
since_shortstatehash, since_shortstatehash,
&StateEventType::RoomEncryption, &StateEventType::RoomEncryption,
"", "",
@@ -1171,12 +1182,12 @@ pub(crate) async fn sync_events_v4_route(
let new_encrypted_room = encrypted_room && since_encryption.is_none(); let new_encrypted_room = encrypted_room && since_encryption.is_none();
if encrypted_room { if encrypted_room {
let current_state_ids = services() let current_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(current_shortstatehash) .state_full_ids(current_shortstatehash)
.await?; .await?;
let since_state_ids = services() let since_state_ids = services
.rooms .rooms
.state_accessor .state_accessor
.state_full_ids(since_shortstatehash) .state_full_ids(since_shortstatehash)
@@ -1184,7 +1195,7 @@ pub(crate) async fn sync_events_v4_route(
for (key, id) in current_state_ids { for (key, id) in current_state_ids {
if since_state_ids.get(&key) != Some(&id) { if since_state_ids.get(&key) != Some(&id) {
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else { let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id); error!("Pdu in state not found: {}", id);
continue; continue;
}; };
@@ -1205,7 +1216,7 @@ pub(crate) async fn sync_events_v4_route(
match new_membership { match new_membership {
MembershipState::Join => { MembershipState::Join => {
// A new user joined an encrypted room // A new user joined an encrypted room
if !share_encrypted_room(&sender_user, &user_id, room_id)? { if !share_encrypted_room(&services, &sender_user, &user_id, room_id)? {
device_list_changes.insert(user_id); device_list_changes.insert(user_id);
} }
}, },
@@ -1222,7 +1233,7 @@ pub(crate) async fn sync_events_v4_route(
if joined_since_last_sync || new_encrypted_room { if joined_since_last_sync || new_encrypted_room {
// If the user is in a new encrypted room, give them all joined users // If the user is in a new encrypted room, give them all joined users
device_list_changes.extend( device_list_changes.extend(
services() services
.rooms .rooms
.state_cache .state_cache
.room_members(room_id) .room_members(room_id)
@@ -1234,7 +1245,7 @@ pub(crate) async fn sync_events_v4_route(
.filter(|user_id| { .filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target // Only send keys if the sender doesn't share an encrypted room with the target
// already // already
!share_encrypted_room(&sender_user, user_id, room_id).unwrap_or(false) !share_encrypted_room(&services, &sender_user, user_id, room_id).unwrap_or(false)
}), }),
); );
} }
@@ -1242,21 +1253,21 @@ pub(crate) async fn sync_events_v4_route(
} }
// Look for device list updates in this room // Look for device list updates in this room
device_list_changes.extend( device_list_changes.extend(
services() services
.users .users
.keys_changed(room_id.as_ref(), globalsince, None) .keys_changed(room_id.as_ref(), globalsince, None)
.filter_map(Result::ok), .filter_map(Result::ok),
); );
} }
for user_id in left_encrypted_users { for user_id in left_encrypted_users {
let dont_share_encrypted_room = services() let dont_share_encrypted_room = services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])? .get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(Result::ok) .filter_map(Result::ok)
.filter_map(|other_room_id| { .filter_map(|other_room_id| {
Some( Some(
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&other_room_id, &StateEventType::RoomEncryption, "") .room_state_get(&other_room_id, &StateEventType::RoomEncryption, "")
@@ -1336,7 +1347,7 @@ pub(crate) async fn sync_events_v4_route(
); );
if let Some(conn_id) = &body.conn_id { if let Some(conn_id) = &body.conn_id {
services().users.update_sync_known_rooms( services.users.update_sync_known_rooms(
sender_user.clone(), sender_user.clone(),
sender_device.clone(), sender_device.clone(),
conn_id.clone(), conn_id.clone(),
@@ -1349,7 +1360,7 @@ pub(crate) async fn sync_events_v4_route(
let mut known_subscription_rooms = BTreeSet::new(); let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions { for (room_id, room) in &body.room_subscriptions {
if !services().rooms.metadata.exists(room_id)? { if !services.rooms.metadata.exists(room_id)? {
continue; continue;
} }
let todo_room = todo_rooms let todo_room = todo_rooms
@@ -1375,7 +1386,7 @@ pub(crate) async fn sync_events_v4_route(
} }
if let Some(conn_id) = &body.conn_id { if let Some(conn_id) = &body.conn_id {
services().users.update_sync_known_rooms( services.users.update_sync_known_rooms(
sender_user.clone(), sender_user.clone(),
sender_device.clone(), sender_device.clone(),
conn_id.clone(), conn_id.clone(),
@@ -1386,7 +1397,7 @@ pub(crate) async fn sync_events_v4_route(
} }
if let Some(conn_id) = &body.conn_id { if let Some(conn_id) = &body.conn_id {
services().users.update_sync_subscriptions( services.users.update_sync_subscriptions(
sender_user.clone(), sender_user.clone(),
sender_device.clone(), sender_device.clone(),
conn_id.clone(), conn_id.clone(),
@@ -1398,7 +1409,8 @@ pub(crate) async fn sync_events_v4_route(
for (room_id, (required_state_request, timeline_limit, roomsince)) in &todo_rooms { for (room_id, (required_state_request, timeline_limit, roomsince)) in &todo_rooms {
let roomsincecount = PduCount::Normal(*roomsince); let roomsincecount = PduCount::Normal(*roomsince);
let (timeline_pdus, limited) = load_timeline(&sender_user, room_id, roomsincecount, *timeline_limit)?; let (timeline_pdus, limited) =
load_timeline(&services, &sender_user, room_id, roomsincecount, *timeline_limit)?;
if roomsince != &0 && timeline_pdus.is_empty() { if roomsince != &0 && timeline_pdus.is_empty() {
continue; continue;
@@ -1431,7 +1443,7 @@ pub(crate) async fn sync_events_v4_route(
let required_state = required_state_request let required_state = required_state_request
.iter() .iter()
.map(|state| { .map(|state| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(room_id, &state.0, &state.1) .room_state_get(room_id, &state.0, &state.1)
@@ -1442,7 +1454,7 @@ pub(crate) async fn sync_events_v4_route(
.collect(); .collect();
// Heroes // Heroes
let heroes = services() let heroes = services
.rooms .rooms
.state_cache .state_cache
.room_members(room_id) .room_members(room_id)
@@ -1450,7 +1462,7 @@ pub(crate) async fn sync_events_v4_route(
.filter(|member| member != &sender_user) .filter(|member| member != &sender_user)
.map(|member| { .map(|member| {
Ok::<_, Error>( Ok::<_, Error>(
services() services
.rooms .rooms
.state_accessor .state_accessor
.get_member(room_id, &member)? .get_member(room_id, &member)?
@@ -1491,11 +1503,11 @@ pub(crate) async fn sync_events_v4_route(
rooms.insert( rooms.insert(
room_id.clone(), room_id.clone(),
sync_events::v4::SlidingSyncRoom { sync_events::v4::SlidingSyncRoom {
name: services().rooms.state_accessor.get_name(room_id)?.or(name), name: services.rooms.state_accessor.get_name(room_id)?.or(name),
avatar: if let Some(heroes_avatar) = heroes_avatar { avatar: if let Some(heroes_avatar) = heroes_avatar {
ruma::JsOption::Some(heroes_avatar) ruma::JsOption::Some(heroes_avatar)
} else { } else {
match services().rooms.state_accessor.get_avatar(room_id)? { match services.rooms.state_accessor.get_avatar(room_id)? {
ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url), ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
ruma::JsOption::Null => ruma::JsOption::Null, ruma::JsOption::Null => ruma::JsOption::Null,
ruma::JsOption::Undefined => ruma::JsOption::Undefined, ruma::JsOption::Undefined => ruma::JsOption::Undefined,
@@ -1506,7 +1518,7 @@ pub(crate) async fn sync_events_v4_route(
invite_state: None, invite_state: None,
unread_notifications: UnreadNotificationsCount { unread_notifications: UnreadNotificationsCount {
highlight_count: Some( highlight_count: Some(
services() services
.rooms .rooms
.user .user
.highlight_count(&sender_user, room_id)? .highlight_count(&sender_user, room_id)?
@@ -1514,7 +1526,7 @@ pub(crate) async fn sync_events_v4_route(
.expect("notification count can't go that high"), .expect("notification count can't go that high"),
), ),
notification_count: Some( notification_count: Some(
services() services
.rooms .rooms
.user .user
.notification_count(&sender_user, room_id)? .notification_count(&sender_user, room_id)?
@@ -1527,7 +1539,7 @@ pub(crate) async fn sync_events_v4_route(
prev_batch, prev_batch,
limited, limited,
joined_count: Some( joined_count: Some(
services() services
.rooms .rooms
.state_cache .state_cache
.room_joined_count(room_id)? .room_joined_count(room_id)?
@@ -1536,7 +1548,7 @@ pub(crate) async fn sync_events_v4_route(
.unwrap_or_else(|_| uint!(0)), .unwrap_or_else(|_| uint!(0)),
), ),
invited_count: Some( invited_count: Some(
services() services
.rooms .rooms
.state_cache .state_cache
.room_invited_count(room_id)? .room_invited_count(room_id)?
@@ -1571,7 +1583,7 @@ pub(crate) async fn sync_events_v4_route(
extensions: sync_events::v4::Extensions { extensions: sync_events::v4::Extensions {
to_device: if body.extensions.to_device.enabled.unwrap_or(false) { to_device: if body.extensions.to_device.enabled.unwrap_or(false) {
Some(sync_events::v4::ToDevice { Some(sync_events::v4::ToDevice {
events: services() events: services
.users .users
.get_to_device_events(&sender_user, &sender_device)?, .get_to_device_events(&sender_user, &sender_device)?,
next_batch: next_batch.to_string(), next_batch: next_batch.to_string(),
@@ -1584,7 +1596,7 @@ pub(crate) async fn sync_events_v4_route(
changed: device_list_changes.into_iter().collect(), changed: device_list_changes.into_iter().collect(),
left: device_list_left.into_iter().collect(), left: device_list_left.into_iter().collect(),
}, },
device_one_time_keys_count: services() device_one_time_keys_count: services
.users .users
.count_one_time_keys(&sender_user, &sender_device)?, .count_one_time_keys(&sender_user, &sender_device)?,
// Fallback keys are not yet supported // Fallback keys are not yet supported
@@ -1592,7 +1604,7 @@ pub(crate) async fn sync_events_v4_route(
}, },
account_data: sync_events::v4::AccountData { account_data: sync_events::v4::AccountData {
global: if body.extensions.account_data.enabled.unwrap_or(false) { global: if body.extensions.account_data.enabled.unwrap_or(false) {
services() services
.account_data .account_data
.changes_since(None, &sender_user, globalsince)? .changes_since(None, &sender_user, globalsince)?
.into_iter() .into_iter()
+16 -9
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State;
use ruma::{ use ruma::{
api::client::tag::{create_tag, delete_tag, get_tags}, api::client::tag::{create_tag, delete_tag, get_tags},
events::{ events::{
@@ -8,17 +9,19 @@ use ruma::{
}, },
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}` /// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}`
/// ///
/// Adds a tag to the room. /// Adds a tag to the room.
/// ///
/// - Inserts the tag into the tag event of the room account data. /// - Inserts the tag into the tag event of the room account data.
pub(crate) async fn update_tag_route(body: Ruma<create_tag::v3::Request>) -> Result<create_tag::v3::Response> { pub(crate) async fn update_tag_route(
State(services): State<crate::State>, body: Ruma<create_tag::v3::Request>,
) -> Result<create_tag::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services() let event = services
.account_data .account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?; .get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
@@ -38,7 +41,7 @@ pub(crate) async fn update_tag_route(body: Ruma<create_tag::v3::Request>) -> Res
.tags .tags
.insert(body.tag.clone().into(), body.tag_info.clone()); .insert(body.tag.clone().into(), body.tag_info.clone());
services().account_data.update( services.account_data.update(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@@ -53,10 +56,12 @@ pub(crate) async fn update_tag_route(body: Ruma<create_tag::v3::Request>) -> Res
/// Deletes a tag from the room. /// Deletes a tag from the room.
/// ///
/// - Removes the tag from the tag event of the room account data. /// - Removes the tag from the tag event of the room account data.
pub(crate) async fn delete_tag_route(body: Ruma<delete_tag::v3::Request>) -> Result<delete_tag::v3::Response> { pub(crate) async fn delete_tag_route(
State(services): State<crate::State>, body: Ruma<delete_tag::v3::Request>,
) -> Result<delete_tag::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services() let event = services
.account_data .account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?; .get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
@@ -73,7 +78,7 @@ pub(crate) async fn delete_tag_route(body: Ruma<delete_tag::v3::Request>) -> Res
tags_event.content.tags.remove(&body.tag.clone().into()); tags_event.content.tags.remove(&body.tag.clone().into());
services().account_data.update( services.account_data.update(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@@ -88,10 +93,12 @@ pub(crate) async fn delete_tag_route(body: Ruma<delete_tag::v3::Request>) -> Res
/// Returns tags on the room. /// Returns tags on the room.
/// ///
/// - Gets the tag event of the room account data. /// - Gets the tag event of the room account data.
pub(crate) async fn get_tags_route(body: Ruma<get_tags::v3::Request>) -> Result<get_tags::v3::Response> { pub(crate) async fn get_tags_route(
State(services): State<crate::State>, body: Ruma<get_tags::v3::Request>,
) -> Result<get_tags::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services() let event = services
.account_data .account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?; .get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
+7 -4
View File
@@ -1,12 +1,15 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::{error::ErrorKind, threads::get_threads}, api::client::{error::ErrorKind, threads::get_threads},
uint, uint,
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/rooms/{roomId}/threads` /// # `GET /_matrix/client/r0/rooms/{roomId}/threads`
pub(crate) async fn get_threads_route(body: Ruma<get_threads::v1::Request>) -> Result<get_threads::v1::Response> { pub(crate) async fn get_threads_route(
State(services): State<crate::State>, body: Ruma<get_threads::v1::Request>,
) -> Result<get_threads::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// Use limit or else 10, with maximum 100 // Use limit or else 10, with maximum 100
@@ -24,14 +27,14 @@ pub(crate) async fn get_threads_route(body: Ruma<get_threads::v1::Request>) -> R
u64::MAX u64::MAX
}; };
let threads = services() let threads = services
.rooms .rooms
.threads .threads
.threads_until(sender_user, &body.room_id, from, &body.include)? .threads_until(sender_user, &body.room_id, from, &body.include)?
.take(limit) .take(limit)
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|(_, pdu)| { .filter(|(_, pdu)| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id) .user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
+12 -10
View File
@@ -1,5 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{Error, Result};
use ruma::{ use ruma::{
api::{ api::{
client::{error::ErrorKind, to_device::send_event_to_device}, client::{error::ErrorKind, to_device::send_event_to_device},
@@ -8,19 +10,19 @@ use ruma::{
to_device::DeviceIdOrAllDevices, to_device::DeviceIdOrAllDevices,
}; };
use crate::{services, user_is_local, Error, Result, Ruma}; use crate::Ruma;
/// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}` /// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}`
/// ///
/// Send a to-device event to a set of client devices. /// Send a to-device event to a set of client devices.
pub(crate) async fn send_event_to_device_route( pub(crate) async fn send_event_to_device_route(
body: Ruma<send_event_to_device::v3::Request>, State(services): State<crate::State>, body: Ruma<send_event_to_device::v3::Request>,
) -> Result<send_event_to_device::v3::Response> { ) -> Result<send_event_to_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref(); let sender_device = body.sender_device.as_deref();
// Check if this is a new transaction id // Check if this is a new transaction id
if services() if services
.transaction_ids .transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)? .existing_txnid(sender_user, sender_device, &body.txn_id)?
.is_some() .is_some()
@@ -30,14 +32,14 @@ pub(crate) async fn send_event_to_device_route(
for (target_user_id, map) in &body.messages { for (target_user_id, map) in &body.messages {
for (target_device_id_maybe, event) in map { for (target_device_id_maybe, event) in map {
if !user_is_local(target_user_id) { if !services.globals.user_is_local(target_user_id) {
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
map.insert(target_device_id_maybe.clone(), event.clone()); map.insert(target_device_id_maybe.clone(), event.clone());
let mut messages = BTreeMap::new(); let mut messages = BTreeMap::new();
messages.insert(target_user_id.clone(), map); messages.insert(target_user_id.clone(), map);
let count = services().globals.next_count()?; let count = services.globals.next_count()?;
services().sending.send_edu_server( services.sending.send_edu_server(
target_user_id.server_name(), target_user_id.server_name(),
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent { serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent {
sender: sender_user.clone(), sender: sender_user.clone(),
@@ -53,7 +55,7 @@ pub(crate) async fn send_event_to_device_route(
match target_device_id_maybe { match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => { DeviceIdOrAllDevices::DeviceId(target_device_id) => {
services().users.add_to_device_event( services.users.add_to_device_event(
sender_user, sender_user,
target_user_id, target_user_id,
target_device_id, target_device_id,
@@ -65,8 +67,8 @@ pub(crate) async fn send_event_to_device_route(
}, },
DeviceIdOrAllDevices::AllDevices => { DeviceIdOrAllDevices::AllDevices => {
for target_device_id in services().users.all_device_ids(target_user_id) { for target_device_id in services.users.all_device_ids(target_user_id) {
services().users.add_to_device_event( services.users.add_to_device_event(
sender_user, sender_user,
target_user_id, target_user_id,
&target_device_id?, &target_device_id?,
@@ -82,7 +84,7 @@ pub(crate) async fn send_event_to_device_route(
} }
// Save transaction id with empty data // Save transaction id with empty data
services() services
.transaction_ids .transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, &[])?; .add_txnid(sender_user, sender_device, &body.txn_id, &[])?;
+8 -7
View File
@@ -1,18 +1,19 @@
use axum::extract::State;
use ruma::api::client::{error::ErrorKind, typing::create_typing_event}; use ruma::api::client::{error::ErrorKind, typing::create_typing_event};
use crate::{services, utils, Error, Result, Ruma}; use crate::{utils, Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}` /// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}`
/// ///
/// Sets the typing state of the sender user. /// Sets the typing state of the sender user.
pub(crate) async fn create_typing_event_route( pub(crate) async fn create_typing_event_route(
body: Ruma<create_typing_event::v3::Request>, State(services): State<crate::State>, body: Ruma<create_typing_event::v3::Request>,
) -> Result<create_typing_event::v3::Response> { ) -> Result<create_typing_event::v3::Response> {
use create_typing_event::v3::Typing; use create_typing_event::v3::Typing;
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services() if !services
.rooms .rooms
.state_cache .state_cache
.is_joined(sender_user, &body.room_id)? .is_joined(sender_user, &body.room_id)?
@@ -23,20 +24,20 @@ pub(crate) async fn create_typing_event_route(
if let Typing::Yes(duration) = body.state { if let Typing::Yes(duration) = body.state {
let duration = utils::clamp( let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX), duration.as_millis().try_into().unwrap_or(u64::MAX),
services() services
.globals .globals
.config .config
.typing_client_timeout_min_s .typing_client_timeout_min_s
.checked_mul(1000) .checked_mul(1000)
.unwrap(), .unwrap(),
services() services
.globals .globals
.config .config
.typing_client_timeout_max_s .typing_client_timeout_max_s
.checked_mul(1000) .checked_mul(1000)
.unwrap(), .unwrap(),
); );
services() services
.rooms .rooms
.typing .typing
.typing_add( .typing_add(
@@ -48,7 +49,7 @@ pub(crate) async fn create_typing_event_route(
) )
.await?; .await?;
} else { } else {
services() services
.rooms .rooms
.typing .typing
.typing_remove(sender_user, &body.room_id) .typing_remove(sender_user, &body.room_id)
+26 -26
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::warn; use conduit::warn;
use ruma::{ use ruma::{
@@ -6,7 +7,7 @@ use ruma::{
OwnedRoomId, OwnedRoomId,
}; };
use crate::{services, Error, Result, Ruma, RumaResponse}; use crate::{Error, Result, Ruma, RumaResponse};
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms` /// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
/// ///
@@ -17,7 +18,8 @@ use crate::{services, Error, Result, Ruma, RumaResponse};
/// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666) /// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")] #[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
pub(crate) async fn get_mutual_rooms_route( pub(crate) async fn get_mutual_rooms_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<mutual_rooms::unstable::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<mutual_rooms::unstable::Request>,
) -> Result<mutual_rooms::unstable::Response> { ) -> Result<mutual_rooms::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -28,14 +30,14 @@ pub(crate) async fn get_mutual_rooms_route(
)); ));
} }
if !services().users.exists(&body.user_id)? { if !services.users.exists(&body.user_id)? {
return Ok(mutual_rooms::unstable::Response { return Ok(mutual_rooms::unstable::Response {
joined: vec![], joined: vec![],
next_batch_token: None, next_batch_token: None,
}); });
} }
let mutual_rooms: Vec<OwnedRoomId> = services() let mutual_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])? .get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
@@ -58,9 +60,10 @@ pub(crate) async fn get_mutual_rooms_route(
/// ///
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266) /// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
pub(crate) async fn get_room_summary_legacy( pub(crate) async fn get_room_summary_legacy(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_summary::msc3266::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<RumaResponse<get_summary::msc3266::Response>> { ) -> Result<RumaResponse<get_summary::msc3266::Response>> {
get_room_summary(InsecureClientIp(client), body) get_room_summary(State(services), InsecureClientIp(client), body)
.await .await
.map(RumaResponse) .map(RumaResponse)
} }
@@ -74,22 +77,19 @@ pub(crate) async fn get_room_summary_legacy(
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266) /// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
#[tracing::instrument(skip_all, fields(%client), name = "room_summary")] #[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
pub(crate) async fn get_room_summary( pub(crate) async fn get_room_summary(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_summary::msc3266::Request>, State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_summary::msc3266::Request>,
) -> Result<get_summary::msc3266::Response> { ) -> Result<get_summary::msc3266::Response> {
let sender_user = body.sender_user.as_ref(); let sender_user = body.sender_user.as_ref();
let room_id = services() let room_id = services.rooms.alias.resolve(&body.room_id_or_alias).await?;
.rooms
.alias
.resolve(&body.room_id_or_alias)
.await?;
if !services().rooms.metadata.exists(&room_id)? { if !services.rooms.metadata.exists(&room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server")); return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server"));
} }
if sender_user.is_none() if sender_user.is_none()
&& !services() && !services
.rooms .rooms
.state_accessor .state_accessor
.is_world_readable(&room_id) .is_world_readable(&room_id)
@@ -103,25 +103,25 @@ pub(crate) async fn get_room_summary(
Ok(get_summary::msc3266::Response { Ok(get_summary::msc3266::Response {
room_id: room_id.clone(), room_id: room_id.clone(),
canonical_alias: services() canonical_alias: services
.rooms .rooms
.state_accessor .state_accessor
.get_canonical_alias(&room_id) .get_canonical_alias(&room_id)
.unwrap_or(None), .unwrap_or(None),
avatar_url: services() avatar_url: services
.rooms .rooms
.state_accessor .state_accessor
.get_avatar(&room_id)? .get_avatar(&room_id)?
.into_option() .into_option()
.unwrap_or_default() .unwrap_or_default()
.url, .url,
guest_can_join: services().rooms.state_accessor.guest_can_join(&room_id)?, guest_can_join: services.rooms.state_accessor.guest_can_join(&room_id)?,
name: services() name: services
.rooms .rooms
.state_accessor .state_accessor
.get_name(&room_id) .get_name(&room_id)
.unwrap_or(None), .unwrap_or(None),
num_joined_members: services() num_joined_members: services
.rooms .rooms
.state_cache .state_cache
.room_joined_count(&room_id) .room_joined_count(&room_id)
@@ -132,21 +132,21 @@ pub(crate) async fn get_room_summary(
}) })
.try_into() .try_into()
.expect("user count should not be that big"), .expect("user count should not be that big"),
topic: services() topic: services
.rooms .rooms
.state_accessor .state_accessor
.get_room_topic(&room_id) .get_room_topic(&room_id)
.unwrap_or(None), .unwrap_or(None),
world_readable: services() world_readable: services
.rooms .rooms
.state_accessor .state_accessor
.is_world_readable(&room_id) .is_world_readable(&room_id)
.unwrap_or(false), .unwrap_or(false),
join_rule: services().rooms.state_accessor.get_join_rule(&room_id)?.0, join_rule: services.rooms.state_accessor.get_join_rule(&room_id)?.0,
room_type: services().rooms.state_accessor.get_room_type(&room_id)?, room_type: services.rooms.state_accessor.get_room_type(&room_id)?,
room_version: Some(services().rooms.state.get_room_version(&room_id)?), room_version: Some(services.rooms.state.get_room_version(&room_id)?),
membership: if let Some(sender_user) = sender_user { membership: if let Some(sender_user) = sender_user {
services() services
.rooms .rooms
.state_accessor .state_accessor
.get_member(&room_id, sender_user)? .get_member(&room_id, sender_user)?
@@ -154,7 +154,7 @@ pub(crate) async fn get_room_summary(
} else { } else {
None None
}, },
encryption: services() encryption: services
.rooms .rooms
.state_accessor .state_accessor
.get_room_encryption(&room_id) .get_room_encryption(&room_id)
+16 -14
View File
@@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::{response::IntoResponse, Json}; use axum::{extract::State, response::IntoResponse, Json};
use ruma::api::client::{ use ruma::api::client::{
discovery::{ discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
@@ -10,7 +10,7 @@ use ruma::api::client::{
error::ErrorKind, error::ErrorKind,
}; };
use crate::{services, Error, Result, Ruma}; use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/versions` /// # `GET /_matrix/client/versions`
/// ///
@@ -62,9 +62,9 @@ pub(crate) async fn get_supported_versions_route(
/// ///
/// Returns the .well-known URL if it is configured, otherwise returns 404. /// Returns the .well-known URL if it is configured, otherwise returns 404.
pub(crate) async fn well_known_client( pub(crate) async fn well_known_client(
_body: Ruma<discover_homeserver::Request>, State(services): State<crate::State>, _body: Ruma<discover_homeserver::Request>,
) -> Result<discover_homeserver::Response> { ) -> Result<discover_homeserver::Response> {
let client_url = match services().globals.well_known_client() { let client_url = match services.globals.well_known_client() {
Some(url) => url.to_string(), Some(url) => url.to_string(),
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
}; };
@@ -84,22 +84,24 @@ pub(crate) async fn well_known_client(
/// # `GET /.well-known/matrix/support` /// # `GET /.well-known/matrix/support`
/// ///
/// Server support contact and support page of a homeserver's domain. /// Server support contact and support page of a homeserver's domain.
pub(crate) async fn well_known_support(_body: Ruma<discover_support::Request>) -> Result<discover_support::Response> { pub(crate) async fn well_known_support(
let support_page = services() State(services): State<crate::State>, _body: Ruma<discover_support::Request>,
) -> Result<discover_support::Response> {
let support_page = services
.globals .globals
.well_known_support_page() .well_known_support_page()
.as_ref() .as_ref()
.map(ToString::to_string); .map(ToString::to_string);
let role = services().globals.well_known_support_role().clone(); let role = services.globals.well_known_support_role().clone();
// support page or role must be either defined for this to be valid // support page or role must be either defined for this to be valid
if support_page.is_none() && role.is_none() { if support_page.is_none() && role.is_none() {
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")); return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
} }
let email_address = services().globals.well_known_support_email().clone(); let email_address = services.globals.well_known_support_email().clone();
let matrix_id = services().globals.well_known_support_mxid().clone(); let matrix_id = services.globals.well_known_support_mxid().clone();
// if a role is specified, an email address or matrix id is required // if a role is specified, an email address or matrix id is required
if role.is_some() && (email_address.is_none() && matrix_id.is_none()) { if role.is_some() && (email_address.is_none() && matrix_id.is_none()) {
@@ -134,10 +136,10 @@ pub(crate) async fn well_known_support(_body: Ruma<discover_support::Request>) -
/// ///
/// Endpoint provided by sliding sync proxy used by some clients such as Element /// Endpoint provided by sliding sync proxy used by some clients such as Element
/// Web as a non-standard health check. /// Web as a non-standard health check.
pub(crate) async fn syncv3_client_server_json() -> Result<impl IntoResponse> { pub(crate) async fn syncv3_client_server_json(State(services): State<crate::State>) -> Result<impl IntoResponse> {
let server_url = match services().globals.well_known_client() { let server_url = match services.globals.well_known_client() {
Some(url) => url.to_string(), Some(url) => url.to_string(),
None => match services().globals.well_known_server() { None => match services.globals.well_known_server() {
Some(url) => url.to_string(), Some(url) => url.to_string(),
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
@@ -165,8 +167,8 @@ pub(crate) async fn conduwuit_server_version() -> Result<impl IntoResponse> {
/// conduwuit-specific API to return the amount of users registered on this /// conduwuit-specific API to return the amount of users registered on this
/// homeserver. Endpoint is disabled if federation is disabled for privacy. This /// homeserver. Endpoint is disabled if federation is disabled for privacy. This
/// only includes active users (not deactivated, no guests, etc) /// only includes active users (not deactivated, no guests, etc)
pub(crate) async fn conduwuit_local_user_count() -> Result<impl IntoResponse> { pub(crate) async fn conduwuit_local_user_count(State(services): State<crate::State>) -> Result<impl IntoResponse> {
let user_count = services().users.list_local_users()?.len(); let user_count = services.users.list_local_users()?.len();
Ok(Json(serde_json::json!({ Ok(Json(serde_json::json!({
"count": user_count "count": user_count
+11 -8
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{ use ruma::{
api::client::user_directory::search_users, api::client::user_directory::search_users,
events::{ events::{
@@ -6,7 +7,7 @@ use ruma::{
}, },
}; };
use crate::{services, Result, Ruma}; use crate::{Result, Ruma};
/// # `POST /_matrix/client/r0/user_directory/search` /// # `POST /_matrix/client/r0/user_directory/search`
/// ///
@@ -14,18 +15,20 @@ use crate::{services, Result, Ruma};
/// ///
/// - Hides any local users that aren't in any public rooms (i.e. those that /// - Hides any local users that aren't in any public rooms (i.e. those that
/// have the join rule set to public) and don't share a room with the sender /// have the join rule set to public) and don't share a room with the sender
pub(crate) async fn search_users_route(body: Ruma<search_users::v3::Request>) -> Result<search_users::v3::Response> { pub(crate) async fn search_users_route(
State(services): State<crate::State>, body: Ruma<search_users::v3::Request>,
) -> Result<search_users::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let limit = usize::try_from(body.limit).unwrap_or(10); // default limit is 10 let limit = usize::try_from(body.limit).unwrap_or(10); // default limit is 10
let mut users = services().users.iter().filter_map(|user_id| { let mut users = services.users.iter().filter_map(|user_id| {
// Filter out buggy users (they should not exist, but you never know...) // Filter out buggy users (they should not exist, but you never know...)
let user_id = user_id.ok()?; let user_id = user_id.ok()?;
let user = search_users::v3::User { let user = search_users::v3::User {
user_id: user_id.clone(), user_id: user_id.clone(),
display_name: services().users.displayname(&user_id).ok()?, display_name: services.users.displayname(&user_id).ok()?,
avatar_url: services().users.avatar_url(&user_id).ok()?, avatar_url: services.users.avatar_url(&user_id).ok()?,
}; };
let user_id_matches = user let user_id_matches = user
@@ -50,13 +53,13 @@ pub(crate) async fn search_users_route(body: Ruma<search_users::v3::Request>) ->
// It's a matching user, but is the sender allowed to see them? // It's a matching user, but is the sender allowed to see them?
let mut user_visible = false; let mut user_visible = false;
let user_is_in_public_rooms = services() let user_is_in_public_rooms = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.any(|room| { .any(|room| {
services() services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get(&room, &StateEventType::RoomJoinRules, "") .room_state_get(&room, &StateEventType::RoomJoinRules, "")
@@ -71,7 +74,7 @@ pub(crate) async fn search_users_route(body: Ruma<search_users::v3::Request>) ->
if user_is_in_public_rooms { if user_is_in_public_rooms {
user_visible = true; user_visible = true;
} else { } else {
let user_is_in_shared_rooms = services() let user_is_in_shared_rooms = services
.rooms .rooms
.user .user
.get_shared_rooms(vec![sender_user.clone(), user_id]) .get_shared_rooms(vec![sender_user.clone(), user_id])
+16 -10
View File
@@ -1,12 +1,13 @@
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use axum::extract::State;
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use conduit::utils; use conduit::{utils, Err};
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch, UserId}; use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch, UserId};
use sha1::Sha1; use sha1::Sha1;
use crate::{services, Result, Ruma}; use crate::{Result, Ruma};
const RANDOM_USER_ID_LENGTH: usize = 10; const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -16,14 +17,19 @@ type HmacSha1 = Hmac<Sha1>;
/// ///
/// TODO: Returns information about the recommended turn server. /// TODO: Returns information about the recommended turn server.
pub(crate) async fn turn_server_route( pub(crate) async fn turn_server_route(
body: Ruma<get_turn_server_info::v3::Request>, State(services): State<crate::State>, body: Ruma<get_turn_server_info::v3::Request>,
) -> Result<get_turn_server_info::v3::Response> { ) -> Result<get_turn_server_info::v3::Response> {
let turn_secret = services().globals.turn_secret().clone(); // MSC4166: return M_NOT_FOUND 404 if no TURN URIs are specified in any way
if services.server.config.turn_uris.is_empty() {
return Err!(Request(NotFound("Not Found")));
}
let turn_secret = services.globals.turn_secret().clone();
let (username, password) = if !turn_secret.is_empty() { let (username, password) = if !turn_secret.is_empty() {
let expiry = SecondsSinceUnixEpoch::from_system_time( let expiry = SecondsSinceUnixEpoch::from_system_time(
SystemTime::now() SystemTime::now()
.checked_add(Duration::from_secs(services().globals.turn_ttl())) .checked_add(Duration::from_secs(services.globals.turn_ttl()))
.expect("TURN TTL should not get this high"), .expect("TURN TTL should not get this high"),
) )
.expect("time is valid"); .expect("time is valid");
@@ -31,7 +37,7 @@ pub(crate) async fn turn_server_route(
let user = body.sender_user.unwrap_or_else(|| { let user = body.sender_user.unwrap_or_else(|| {
UserId::parse_with_server_name( UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(), utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
&services().globals.config.server_name, &services.globals.config.server_name,
) )
.unwrap() .unwrap()
}); });
@@ -46,15 +52,15 @@ pub(crate) async fn turn_server_route(
(username, password) (username, password)
} else { } else {
( (
services().globals.turn_username().clone(), services.globals.turn_username().clone(),
services().globals.turn_password().clone(), services.globals.turn_password().clone(),
) )
}; };
Ok(get_turn_server_info::v3::Response { Ok(get_turn_server_info::v3::Response {
username, username,
password, password,
uris: services().globals.turn_uris().to_vec(), uris: services.globals.turn_uris().to_vec(),
ttl: Duration::from_secs(services().globals.turn_ttl()), ttl: Duration::from_secs(services.globals.turn_ttl()),
}) })
} }
+4 -2
View File
@@ -1,3 +1,5 @@
#![recursion_limit = "192"]
pub mod client; pub mod client;
pub mod router; pub mod router;
pub mod server; pub mod server;
@@ -5,8 +7,8 @@ pub mod server;
extern crate conduit_core as conduit; extern crate conduit_core as conduit;
extern crate conduit_service as service; extern crate conduit_service as service;
pub(crate) use conduit::{debug_info, debug_warn, pdu::PduEvent, utils, Error, Result}; pub(crate) use conduit::{debug_info, pdu::PduEvent, utils, Error, Result};
pub(crate) use service::{services, user_is_local}; pub(crate) use service::services;
pub use crate::router::State; pub use crate::router::State;
pub(crate) use crate::router::{Ruma, RumaResponse}; pub(crate) use crate::router::{Ruma, RumaResponse};
+3 -1
View File
@@ -4,6 +4,8 @@ mod handler;
mod request; mod request;
mod response; mod response;
use std::sync::Arc;
use axum::{ use axum::{
response::IntoResponse, response::IntoResponse,
routing::{any, get, post}, routing::{any, get, post},
@@ -16,7 +18,7 @@ use self::handler::RouterExt;
pub(super) use self::{args::Args as Ruma, response::RumaResponse}; pub(super) use self::{args::Args as Ruma, response::RumaResponse};
use crate::{client, server}; use crate::{client, server};
pub type State = &'static service::Services; pub type State = Arc<service::Services>;
pub fn build(router: Router<State>, server: &Server) -> Router<State> { pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config; let config = &server.config;
+15 -11
View File
@@ -2,11 +2,12 @@ use std::{mem, ops::Deref};
use axum::{async_trait, body::Body, extract::FromRequest}; use axum::{async_trait, body::Body, extract::FromRequest};
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use conduit::{debug, err, trace, Error, Result}; use conduit::{debug, err, trace, utils::string::EMPTY, Error, Result};
use ruma::{api::IncomingRequest, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId}; use ruma::{api::IncomingRequest, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId};
use service::Services;
use super::{auth, auth::Auth, request, request::Request}; use super::{auth, auth::Auth, request, request::Request};
use crate::{service::appservice::RegistrationInfo, services}; use crate::{service::appservice::RegistrationInfo, State};
/// Extractor for Ruma request structs /// Extractor for Ruma request structs
pub(crate) struct Args<T> { pub(crate) struct Args<T> {
@@ -35,18 +36,18 @@ pub(crate) struct Args<T> {
} }
#[async_trait] #[async_trait]
impl<T, S> FromRequest<S, Body> for Args<T> impl<T> FromRequest<State, Body> for Args<T>
where where
T: IncomingRequest, T: IncomingRequest,
{ {
type Rejection = Error; type Rejection = Error;
async fn from_request(request: hyper::Request<Body>, _: &S) -> Result<Self, Self::Rejection> { async fn from_request(request: hyper::Request<Body>, services: &State) -> Result<Self, Self::Rejection> {
let mut request = request::from(request).await?; let mut request = request::from(services, request).await?;
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&request.body).ok(); let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&request.body).ok();
let auth = auth::auth(&mut request, &json_body, &T::METADATA).await?; let auth = auth::auth(services, &mut request, &json_body, &T::METADATA).await?;
Ok(Self { Ok(Self {
body: make_body::<T>(&mut request, &mut json_body, &auth)?, body: make_body::<T>(services, &mut request, &mut json_body, &auth)?,
origin: auth.origin, origin: auth.origin,
sender_user: auth.sender_user, sender_user: auth.sender_user,
sender_device: auth.sender_device, sender_device: auth.sender_device,
@@ -62,13 +63,16 @@ impl<T> Deref for Args<T> {
fn deref(&self) -> &Self::Target { &self.body } fn deref(&self) -> &Self::Target { &self.body }
} }
fn make_body<T>(request: &mut Request, json_body: &mut Option<CanonicalJsonValue>, auth: &Auth) -> Result<T> fn make_body<T>(
services: &Services, request: &mut Request, json_body: &mut Option<CanonicalJsonValue>, auth: &Auth,
) -> Result<T>
where where
T: IncomingRequest, T: IncomingRequest,
{ {
let body = if let Some(CanonicalJsonValue::Object(json_body)) = json_body { let body = if let Some(CanonicalJsonValue::Object(json_body)) = json_body {
let user_id = auth.sender_user.clone().unwrap_or_else(|| { let user_id = auth.sender_user.clone().unwrap_or_else(|| {
UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid") let server_name = services.globals.server_name();
UserId::parse_with_server_name(EMPTY, server_name).expect("valid user_id")
}); });
let uiaa_request = json_body let uiaa_request = json_body
@@ -77,9 +81,9 @@ where
.and_then(|auth| auth.get("session")) .and_then(|auth| auth.get("session"))
.and_then(|session| session.as_str()) .and_then(|session| session.as_str())
.and_then(|session| { .and_then(|session| {
services().uiaa.get_uiaa_request( services.uiaa.get_uiaa_request(
&user_id, &user_id,
&auth.sender_device.clone().unwrap_or_else(|| "".into()), &auth.sender_device.clone().unwrap_or_else(|| EMPTY.into()),
session, session,
) )
}); });
+30 -17
View File
@@ -6,17 +6,17 @@ use axum_extra::{
typed_header::TypedHeaderRejectionReason, typed_header::TypedHeaderRejectionReason,
TypedHeader, TypedHeader,
}; };
use conduit::Err; use conduit::{debug_info, warn, Err, Error, Result};
use http::uri::PathAndQuery; use http::uri::PathAndQuery;
use ruma::{ use ruma::{
api::{client::error::ErrorKind, AuthScheme, Metadata}, api::{client::error::ErrorKind, AuthScheme, Metadata},
server_util::authorization::XMatrix, server_util::authorization::XMatrix,
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
}; };
use tracing::warn; use service::Services;
use super::request::Request; use super::request::Request;
use crate::{service::appservice::RegistrationInfo, services, Error, Result}; use crate::service::appservice::RegistrationInfo;
enum Token { enum Token {
Appservice(Box<RegistrationInfo>), Appservice(Box<RegistrationInfo>),
@@ -33,7 +33,7 @@ pub(super) struct Auth {
} }
pub(super) async fn auth( pub(super) async fn auth(
request: &mut Request, json_body: &Option<CanonicalJsonValue>, metadata: &Metadata, services: &Services, request: &mut Request, json_body: &Option<CanonicalJsonValue>, metadata: &Metadata,
) -> Result<Auth> { ) -> Result<Auth> {
let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?; let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?;
let token = match &bearer { let token = match &bearer {
@@ -42,9 +42,9 @@ pub(super) async fn auth(
}; };
let token = if let Some(token) = token { let token = if let Some(token) = token {
if let Some(reg_info) = services().appservice.find_from_token(token).await { if let Some(reg_info) = services.appservice.find_from_token(token).await {
Token::Appservice(Box::new(reg_info)) Token::Appservice(Box::new(reg_info))
} else if let Some((user_id, device_id)) = services().users.find_from_token(token)? { } else if let Some((user_id, device_id)) = services.users.find_from_token(token)? {
Token::User((user_id, OwnedDeviceId::from(device_id))) Token::User((user_id, OwnedDeviceId::from(device_id)))
} else { } else {
Token::Invalid Token::Invalid
@@ -57,7 +57,7 @@ pub(super) async fn auth(
match request.parts.uri.path() { match request.parts.uri.path() {
// TODO: can we check this better? // TODO: can we check this better?
"/_matrix/client/v3/publicRooms" | "/_matrix/client/r0/publicRooms" => { "/_matrix/client/v3/publicRooms" | "/_matrix/client/r0/publicRooms" => {
if !services() if !services
.globals .globals
.config .config
.allow_public_room_directory_without_auth .allow_public_room_directory_without_auth
@@ -98,7 +98,7 @@ pub(super) async fn auth(
)) ))
} }
}, },
(AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(request, info)?), (AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(services, request, info)?),
(AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => { (AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => {
Ok(Auth { Ok(Auth {
origin: None, origin: None,
@@ -110,7 +110,7 @@ pub(super) async fn auth(
(AuthScheme::AccessToken, Token::None) => match request.parts.uri.path() { (AuthScheme::AccessToken, Token::None) => match request.parts.uri.path() {
// TODO: can we check this better? // TODO: can we check this better?
"/_matrix/client/v3/voip/turnServer" | "/_matrix/client/r0/voip/turnServer" => { "/_matrix/client/v3/voip/turnServer" | "/_matrix/client/r0/voip/turnServer" => {
if services().globals.config.turn_allow_guests { if services.globals.config.turn_allow_guests {
Ok(Auth { Ok(Auth {
origin: None, origin: None,
sender_user: None, sender_user: None,
@@ -132,7 +132,7 @@ pub(super) async fn auth(
sender_device: Some(device_id), sender_device: Some(device_id),
appservice_info: None, appservice_info: None,
}), }),
(AuthScheme::ServerSignatures, Token::None) => Ok(auth_server(request, json_body).await?), (AuthScheme::ServerSignatures, Token::None) => Ok(auth_server(services, request, json_body).await?),
(AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional, Token::None) => Ok(Auth { (AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional, Token::None) => Ok(Auth {
sender_user: None, sender_user: None,
sender_device: None, sender_device: None,
@@ -150,7 +150,7 @@ pub(super) async fn auth(
} }
} }
fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Auth> { fn auth_appservice(services: &Services, request: &Request, info: Box<RegistrationInfo>) -> Result<Auth> {
let user_id = request let user_id = request
.query .query
.user_id .user_id
@@ -159,7 +159,7 @@ fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Aut
|| { || {
UserId::parse_with_server_name( UserId::parse_with_server_name(
info.registration.sender_localpart.as_str(), info.registration.sender_localpart.as_str(),
services().globals.server_name(), services.globals.server_name(),
) )
}, },
UserId::parse, UserId::parse,
@@ -170,7 +170,7 @@ fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Aut
return Err(Error::BadRequest(ErrorKind::Exclusive, "User is not in namespace.")); return Err(Error::BadRequest(ErrorKind::Exclusive, "User is not in namespace."));
} }
if !services().users.exists(&user_id)? { if !services.users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::forbidden(), "User does not exist.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "User does not exist."));
} }
@@ -182,8 +182,10 @@ fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Aut
}) })
} }
async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValue>) -> Result<Auth> { async fn auth_server(
if !services().globals.allow_federation() { services: &Services, request: &mut Request, json_body: &Option<CanonicalJsonValue>,
) -> Result<Auth> {
if !services.server.config.allow_federation {
return Err!(Config("allow_federation", "Federation is disabled.")); return Err!(Config("allow_federation", "Federation is disabled."));
} }
@@ -204,6 +206,17 @@ async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValu
})?; })?;
let origin = &x_matrix.origin; let origin = &x_matrix.origin;
if services
.server
.config
.forbidden_remote_server_names
.contains(origin)
{
debug_info!("Refusing to accept inbound federation request to {origin}");
return Err!(Request(Forbidden("Federation with this homeserver is not allowed.")));
}
let signatures = let signatures =
BTreeMap::from_iter([(x_matrix.key.clone(), CanonicalJsonValue::String(x_matrix.sig.to_string()))]); BTreeMap::from_iter([(x_matrix.key.clone(), CanonicalJsonValue::String(x_matrix.sig.to_string()))]);
let signatures = BTreeMap::from_iter([( let signatures = BTreeMap::from_iter([(
@@ -216,7 +229,7 @@ async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValu
), ),
)]); )]);
let server_destination = services().globals.server_name().as_str().to_owned(); let server_destination = services.globals.server_name().as_str().to_owned();
if let Some(destination) = x_matrix.destination.as_ref() { if let Some(destination) = x_matrix.destination.as_ref() {
if destination != &server_destination { if destination != &server_destination {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Invalid authorization.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Invalid authorization."));
@@ -247,7 +260,7 @@ async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValu
request_map.insert("content".to_owned(), json_body.clone()); request_map.insert("content".to_owned(), json_body.clone());
}; };
let keys_result = services() let keys_result = services
.rooms .rooms
.event_handler .event_handler
.fetch_signing_keys_for_server(origin, vec![x_matrix.key.to_string()]) .fetch_signing_keys_for_server(origin, vec![x_matrix.key.to_string()])
+4 -5
View File
@@ -2,11 +2,10 @@ use std::str;
use axum::{extract::Path, RequestExt, RequestPartsExt}; use axum::{extract::Path, RequestExt, RequestPartsExt};
use bytes::Bytes; use bytes::Bytes;
use conduit::err; use conduit::{err, Result};
use http::request::Parts; use http::request::Parts;
use serde::Deserialize; use serde::Deserialize;
use service::Services;
use crate::{services, Result};
#[derive(Deserialize)] #[derive(Deserialize)]
pub(super) struct QueryParams { pub(super) struct QueryParams {
@@ -21,7 +20,7 @@ pub(super) struct Request {
pub(super) parts: Parts, pub(super) parts: Parts,
} }
pub(super) async fn from(request: hyper::Request<axum::body::Body>) -> Result<Request> { pub(super) async fn from(services: &Services, request: hyper::Request<axum::body::Body>) -> Result<Request> {
let limited = request.with_limited_body(); let limited = request.with_limited_body();
let (mut parts, body) = limited.into_parts(); let (mut parts, body) = limited.into_parts();
@@ -30,7 +29,7 @@ pub(super) async fn from(request: hyper::Request<axum::body::Body>) -> Result<Re
let query = let query =
serde_html_form::from_str(query).map_err(|e| err!(Request(Unknown("Failed to read query parameters: {e}"))))?; serde_html_form::from_str(query).map_err(|e| err!(Request(Unknown("Failed to read query parameters: {e}"))))?;
let max_body_size = services().globals.config.max_request_size; let max_body_size = services.globals.config.max_request_size;
let body = axum::body::to_bytes(body, max_body_size) let body = axum::body::to_bytes(body, max_body_size)
.await .await
+13 -11
View File
@@ -1,9 +1,9 @@
use axum::extract::State;
use conduit::{Error, Result}; use conduit::{Error, Result};
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation::backfill::get_backfill}, api::{client::error::ErrorKind, federation::backfill::get_backfill},
uint, user_id, MilliSecondsSinceUnixEpoch, uint, user_id, MilliSecondsSinceUnixEpoch,
}; };
use service::{sending::convert_to_outgoing_federation_event, services};
use crate::Ruma; use crate::Ruma;
@@ -11,19 +11,21 @@ use crate::Ruma;
/// ///
/// Retrieves events from before the sender joined the room, if the room's /// Retrieves events from before the sender joined the room, if the room's
/// history visibility allows. /// history visibility allows.
pub(crate) async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result<get_backfill::v1::Response> { pub(crate) async fn get_backfill_route(
State(services): State<crate::State>, body: Ruma<get_backfill::v1::Request>,
) -> Result<get_backfill::v1::Response> {
let origin = body.origin.as_ref().expect("server is authenticated"); let origin = body.origin.as_ref().expect("server is authenticated");
services() services
.rooms .rooms
.event_handler .event_handler
.acl_check(origin, &body.room_id)?; .acl_check(origin, &body.room_id)?;
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.is_world_readable(&body.room_id)? .is_world_readable(&body.room_id)?
&& !services() && !services
.rooms .rooms
.state_cache .state_cache
.server_in_room(origin, &body.room_id)? .server_in_room(origin, &body.room_id)?
@@ -34,7 +36,7 @@ pub(crate) async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) ->
let until = body let until = body
.v .v
.iter() .iter()
.map(|event_id| services().rooms.timeline.get_pdu_count(event_id)) .map(|event_id| services.rooms.timeline.get_pdu_count(event_id))
.filter_map(|r| r.ok().flatten()) .filter_map(|r| r.ok().flatten())
.max() .max()
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event not found."))?; .ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event not found."))?;
@@ -45,7 +47,7 @@ pub(crate) async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) ->
.try_into() .try_into()
.expect("UInt could not be converted to usize"); .expect("UInt could not be converted to usize");
let all_events = services() let all_events = services
.rooms .rooms
.timeline .timeline
.pdus_until(user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)? .pdus_until(user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
@@ -55,20 +57,20 @@ pub(crate) async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) ->
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|(_, e)| { .filter(|(_, e)| {
matches!( matches!(
services() services
.rooms .rooms
.state_accessor .state_accessor
.server_can_see_event(origin, &e.room_id, &e.event_id,), .server_can_see_event(origin, &e.room_id, &e.event_id,),
Ok(true), Ok(true),
) )
}) })
.map(|(_, pdu)| services().rooms.timeline.get_pdu_json(&pdu.event_id)) .map(|(_, pdu)| services.rooms.timeline.get_pdu_json(&pdu.event_id))
.filter_map(|r| r.ok().flatten()) .filter_map(|r| r.ok().flatten())
.map(convert_to_outgoing_federation_event) .map(|pdu| services.sending.convert_to_outgoing_federation_event(pdu))
.collect(); .collect();
Ok(get_backfill::v1::Response { Ok(get_backfill::v1::Response {
origin: services().globals.server_name().to_owned(), origin: services.globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(), origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
pdus: events, pdus: events,
}) })
+10 -11
View File
@@ -1,9 +1,9 @@
use axum::extract::State;
use conduit::{Error, Result}; use conduit::{Error, Result};
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation::event::get_event}, api::{client::error::ErrorKind, federation::event::get_event},
MilliSecondsSinceUnixEpoch, RoomId, MilliSecondsSinceUnixEpoch, RoomId,
}; };
use service::{sending::convert_to_outgoing_federation_event, services};
use crate::Ruma; use crate::Ruma;
@@ -13,10 +13,12 @@ use crate::Ruma;
/// ///
/// - Only works if a user of this server is currently invited or joined the /// - Only works if a user of this server is currently invited or joined the
/// room /// room
pub(crate) async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_event::v1::Response> { pub(crate) async fn get_event_route(
State(services): State<crate::State>, body: Ruma<get_event::v1::Request>,
) -> Result<get_event::v1::Response> {
let origin = body.origin.as_ref().expect("server is authenticated"); let origin = body.origin.as_ref().expect("server is authenticated");
let event = services() let event = services
.rooms .rooms
.timeline .timeline
.get_pdu_json(&body.event_id)? .get_pdu_json(&body.event_id)?
@@ -30,16 +32,13 @@ pub(crate) async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Resul
let room_id = let room_id =
<&RoomId>::try_from(room_id_str).map_err(|_| Error::bad_database("Invalid room_id in event in database."))?; <&RoomId>::try_from(room_id_str).map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
if !services().rooms.state_accessor.is_world_readable(room_id)? if !services.rooms.state_accessor.is_world_readable(room_id)?
&& !services() && !services.rooms.state_cache.server_in_room(origin, room_id)?
.rooms
.state_cache
.server_in_room(origin, room_id)?
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Server is not in room.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Server is not in room."));
} }
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.server_can_see_event(origin, room_id, &body.event_id)? .server_can_see_event(origin, room_id, &body.event_id)?
@@ -48,8 +47,8 @@ pub(crate) async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Resul
} }
Ok(get_event::v1::Response { Ok(get_event::v1::Response {
origin: services().globals.server_name().to_owned(), origin: services.globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(), origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
pdu: convert_to_outgoing_federation_event(event), pdu: services.sending.convert_to_outgoing_federation_event(event),
}) })
} }
+9 -9
View File
@@ -1,11 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use axum::extract::State;
use conduit::{Error, Result}; use conduit::{Error, Result};
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation::authorization::get_event_authorization}, api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
RoomId, RoomId,
}; };
use service::{sending::convert_to_outgoing_federation_event, services};
use crate::Ruma; use crate::Ruma;
@@ -15,20 +15,20 @@ use crate::Ruma;
/// ///
/// - This does not include the event itself /// - This does not include the event itself
pub(crate) async fn get_event_authorization_route( pub(crate) async fn get_event_authorization_route(
body: Ruma<get_event_authorization::v1::Request>, State(services): State<crate::State>, body: Ruma<get_event_authorization::v1::Request>,
) -> Result<get_event_authorization::v1::Response> { ) -> Result<get_event_authorization::v1::Response> {
let origin = body.origin.as_ref().expect("server is authenticated"); let origin = body.origin.as_ref().expect("server is authenticated");
services() services
.rooms .rooms
.event_handler .event_handler
.acl_check(origin, &body.room_id)?; .acl_check(origin, &body.room_id)?;
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.is_world_readable(&body.room_id)? .is_world_readable(&body.room_id)?
&& !services() && !services
.rooms .rooms
.state_cache .state_cache
.server_in_room(origin, &body.room_id)? .server_in_room(origin, &body.room_id)?
@@ -36,7 +36,7 @@ pub(crate) async fn get_event_authorization_route(
return Err(Error::BadRequest(ErrorKind::forbidden(), "Server is not in room.")); return Err(Error::BadRequest(ErrorKind::forbidden(), "Server is not in room."));
} }
let event = services() let event = services
.rooms .rooms
.timeline .timeline
.get_pdu_json(&body.event_id)? .get_pdu_json(&body.event_id)?
@@ -50,7 +50,7 @@ pub(crate) async fn get_event_authorization_route(
let room_id = let room_id =
<&RoomId>::try_from(room_id_str).map_err(|_| Error::bad_database("Invalid room_id in event in database."))?; <&RoomId>::try_from(room_id_str).map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
let auth_chain_ids = services() let auth_chain_ids = services
.rooms .rooms
.auth_chain .auth_chain
.event_ids_iter(room_id, vec![Arc::from(&*body.event_id)]) .event_ids_iter(room_id, vec![Arc::from(&*body.event_id)])
@@ -58,8 +58,8 @@ pub(crate) async fn get_event_authorization_route(
Ok(get_event_authorization::v1::Response { Ok(get_event_authorization::v1::Response {
auth_chain: auth_chain_ids auth_chain: auth_chain_ids
.filter_map(|id| services().rooms.timeline.get_pdu_json(&id).ok()?) .filter_map(|id| services.rooms.timeline.get_pdu_json(&id).ok()?)
.map(convert_to_outgoing_federation_event) .map(|pdu| services.sending.convert_to_outgoing_federation_event(pdu))
.collect(), .collect(),
}) })
} }
+8 -8
View File
@@ -1,9 +1,9 @@
use axum::extract::State;
use conduit::{Error, Result}; use conduit::{Error, Result};
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation::event::get_missing_events}, api::{client::error::ErrorKind, federation::event::get_missing_events},
OwnedEventId, RoomId, OwnedEventId, RoomId,
}; };
use service::{sending::convert_to_outgoing_federation_event, services};
use crate::Ruma; use crate::Ruma;
@@ -11,20 +11,20 @@ use crate::Ruma;
/// ///
/// Retrieves events that the sender is missing. /// Retrieves events that the sender is missing.
pub(crate) async fn get_missing_events_route( pub(crate) async fn get_missing_events_route(
body: Ruma<get_missing_events::v1::Request>, State(services): State<crate::State>, body: Ruma<get_missing_events::v1::Request>,
) -> Result<get_missing_events::v1::Response> { ) -> Result<get_missing_events::v1::Response> {
let origin = body.origin.as_ref().expect("server is authenticated"); let origin = body.origin.as_ref().expect("server is authenticated");
services() services
.rooms .rooms
.event_handler .event_handler
.acl_check(origin, &body.room_id)?; .acl_check(origin, &body.room_id)?;
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.is_world_readable(&body.room_id)? .is_world_readable(&body.room_id)?
&& !services() && !services
.rooms .rooms
.state_cache .state_cache
.server_in_room(origin, &body.room_id)? .server_in_room(origin, &body.room_id)?
@@ -43,7 +43,7 @@ pub(crate) async fn get_missing_events_route(
let mut i: usize = 0; let mut i: usize = 0;
while i < queued_events.len() && events.len() < limit { while i < queued_events.len() && events.len() < limit {
if let Some(pdu) = services().rooms.timeline.get_pdu_json(&queued_events[i])? { if let Some(pdu) = services.rooms.timeline.get_pdu_json(&queued_events[i])? {
let room_id_str = pdu let room_id_str = pdu
.get("room_id") .get("room_id")
.and_then(|val| val.as_str()) .and_then(|val| val.as_str())
@@ -61,7 +61,7 @@ pub(crate) async fn get_missing_events_route(
continue; continue;
} }
if !services() if !services
.rooms .rooms
.state_accessor .state_accessor
.server_can_see_event(origin, &body.room_id, &queued_events[i])? .server_can_see_event(origin, &body.room_id, &queued_events[i])?
@@ -81,7 +81,7 @@ pub(crate) async fn get_missing_events_route(
) )
.map_err(|_| Error::bad_database("Invalid prev_events in event in database."))?, .map_err(|_| Error::bad_database("Invalid prev_events in event in database."))?,
); );
events.push(convert_to_outgoing_federation_event(pdu)); events.push(services.sending.convert_to_outgoing_federation_event(pdu));
} }
i = i.saturating_add(1); i = i.saturating_add(1);
} }

Some files were not shown because too many files have changed in this diff Show More