Compare commits

...

133 Commits

Author SHA1 Message Date
Matthias Ahouansou 69b3d9f7dd support timestamped appservice messaging
Co-authored-by: strawberry <strawberry@puppygock.gay>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-14 20:01:34 -04:00
strawberry 1fb32e8861 nix: bump flake.lock
• Updated input 'attic':
    'github:zhaofengli/attic/6139576a3ce6bb992e0f6c3022528ec233e45f00' (2024-07-09)
  → 'github:zhaofengli/attic/26b9417bde03edc6280d1f7ce709cd619cdb72d4' (2024-08-13)
• Updated input 'cachix':
    'github:cachix/cachix/4e9e71f78b9500fa6210cf1eaa4d75bdbab777c3' (2024-07-02)
  → 'github:cachix/cachix/bd558fd32f55d6df1c3d496bade90335a1a6044d' (2024-08-10)
• Updated input 'cachix/devenv':
    'github:cachix/devenv/bb32aa986f2f695385e54428d0eaf7d05b31466e' (2024-06-30)
  → 'github:cachix/devenv/ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91' (2024-08-08)
• Updated input 'cachix/devenv/pre-commit-hooks':
    follows 'cachix/pre-commit-hooks'
  → follows 'cachix/git-hooks'
• Added input 'cachix/git-hooks':
    'github:cachix/git-hooks.nix/c7012d0c18567c889b948781bc74a501e92275d1' (2024-08-09)
• Added input 'cachix/git-hooks/flake-compat':
    follows 'cachix/flake-compat'
• Added input 'cachix/git-hooks/gitignore':
    'github:hercules-ci/gitignore.nix/637db329424fd7e46cf4185293b9cc8c88c95394' (2024-02-28)
• Added input 'cachix/git-hooks/gitignore/nixpkgs':
    follows 'cachix/git-hooks/nixpkgs'
• Added input 'cachix/git-hooks/nixpkgs':
    follows 'cachix/nixpkgs'
• Added input 'cachix/git-hooks/nixpkgs-stable':
    'github:NixOS/nixpkgs/194846768975b7ad2c4988bdb82572c00222c0d7' (2024-07-07)
• Updated input 'cachix/nixpkgs':
    'github:NixOS/nixpkgs/00d80d13810dbfea8ab4ed1009b09100cca86ba8' (2024-07-01)
  → 'github:NixOS/nixpkgs/cb9a96f23c491c081b38eab96d22fa958043c9fa' (2024-08-04)
• Removed input 'cachix/pre-commit-hooks'
• Removed input 'cachix/pre-commit-hooks/flake-compat'
• Removed input 'cachix/pre-commit-hooks/gitignore'
• Removed input 'cachix/pre-commit-hooks/gitignore/nixpkgs'
• Removed input 'cachix/pre-commit-hooks/nixpkgs'
• Removed input 'cachix/pre-commit-hooks/nixpkgs-stable'
• Updated input 'crane':
    'github:ipetkov/crane/2d83156f23c43598cf44e152c33a59d3892f8b29' (2024-07-09)
  → 'github:ipetkov/crane/4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4' (2024-08-06)
• Updated input 'fenix':
    'github:nix-community/fenix/5087b12a595ee73131a944d922f24d81dae05725' (2024-07-13)
  → 'github:nix-community/fenix/296d44c440302980824c5f3b67e477cf0522e0c1' (2024-08-13)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/ffbc5ad993d5cd2f3b8bcf9a511165470944ab91' (2024-07-11)
  → 'github:rust-lang/rust-analyzer/32a86cb1dad2b208e8f36f1bb50c2e4806b0371f' (2024-08-12)
• Updated input 'liburing':
    'github:axboe/liburing/1d674f83b7d0f07553ac44d99a401b05853d9dbe' (2024-07-12)
  → 'github:axboe/liburing/401b3e4bde2316d35e93b548269c72bbb64adc02' (2024-08-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9' (2024-07-12)
  → 'github:NixOS/nixpkgs/a58bc8ad779655e790115244571758e8de055e3d' (2024-08-11)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-14 02:39:18 -04:00
strawberry 4d2e8dab0b bump various cargo deps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-14 02:39:13 -04:00
strawberry 89dbbf18a5 bump rust toolchain to hotfix 1.80.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-13 19:25:07 -04:00
strawberry 02590b9cd1 dont apply new_user_displayname_suffix to appservice user registrations
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-13 19:25:07 -04:00
strawberry b12f21dce6 reduce unnecessary logging on URL preview and event, use sensible error code for URL previews
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-13 19:25:07 -04:00
strawberry d08f181564 apply forbidden_remote_server_names to outbound sending and inbound federation handling
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-13 19:25:07 -04:00
strawberry 442d950545 dont send duplicate membership update events if pfp and display name are same
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-09 23:20:46 -04:00
strawberry fa9298f427 respect m.room.guest_access on room joins
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-09 23:20:43 -04:00
strawberry 3d32726bbe implement MSC4166, returning M_NOT_FOUND if no TURN URIs are set
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-09 14:41:46 -04:00
strawberry 6662aa21ef bump complement to 6e4426a9e63233f9821a4d2382bfed145244183f
• Updated input 'complement':
    'github:matrix-org/complement/0d14432e010482ea9e13a6f7c47c1533c0c9d62f' (2024-07-10)
  → 'github:matrix-org/complement/6e4426a9e63233f9821a4d2382bfed145244183f' (2024-07-30)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-08 17:18:26 -04:00
Jason Volk 9b9a91f6ef add our_modules() for log capture filter convenience
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 94b805de0b generalize log capture to all admin commands; simplify handler
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk eded585f79 rename some command types for consistency
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk a552321287 add Clap to Error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk f98bfbbdcd add string stream convenience util; add ?Sized bounds on log fmt functors
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk e9ab548445 tweak some tracing log levels
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 15184d1a79 Add span scope names array to capture filter data; optimize values visitor vec.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 0c6bbde25f fix markdown table formatter for log capture
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 13aa353765 add arrayvec as a dependency
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk ac941a801a add interface to query rocksdb properties w/ admin cmd
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk f261d44edb add method to iterate opened database columns
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 849cfdcdfa add rocksdb stats level option with conf item
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
Jason Volk 1470331f7e add repair_mode documentation to example conf
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-08 18:09:21 +00:00
strawberry dcaa96a79f 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-08-07 12:07:01 -04:00
strawberry 101a966922 docs: mention potential DNS issues when using docker
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:06:28 -04:00
strawberry 57940f38ea 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-08-07 12:06:19 -04:00
strawberry 22f8c339fc debian: remove --verbose from postinst
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:06:09 -04:00
strawberry c896acb729 dont consider presence status changes apart of presence spam
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:06:01 -04:00
strawberry 7f1bc490d6 docs: update configuration docs for the new -O option
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:05:27 -04:00
strawberry 1091c30845 docs: mention FreeBSD specific build commandline for deployment
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:05:16 -04:00
strawberry 12d9e454db display success/failure count in get-remote-pdu-list
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:04:51 -04:00
strawberry 82b973a8b1 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-08-07 12:04:41 -04:00
strawberry 71ee31a014 add missing newline in banned room list output
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-07 12:04:12 -04:00
skedastically 69b4ec81b6 update container registry links (#549 / #548)
* gitlab URL

* Update docker.md
2024-08-05 15:23:40 -04:00
Jason Volk fe0969e7ca fix release-max-perf profile [ci skip]
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 13:37:27 +00:00
Jason Volk 2e3e14b045 refactor some additional errors
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:59 +00:00
Jason Volk f51d4237c7 bump cargo
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:59 +00:00
strawberry d8a87258d6 rust: bump console-subscriber to 0.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-01 10:58:58 +00:00
strawberry 3da2242730 bump tokio, use default-features = false for more crates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-08-01 10:58:58 +00:00
Jason Volk 5add9a8c34 support field values in err! macro
Signed-off-by: Jason Volk <jason@zemos.net>
2024-08-01 10:58:58 +00:00
Jason Volk 5b1642f641 split server_keys from event_handler into service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 21:10:22 +00:00
Jason Volk eb94aa7079 remove dangling sending/resolve unit
this was probably un-deleted during a rebase conflict resolution,
but remained outside of the mod tree.

Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 21:10:22 +00:00
Jason Volk 24884c3b9b move media helpers from api to services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk a0a002b17f disable legacy media compat by default; cleanse directory when disabled
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk f632b06e6d use Services instead of Server for middleware function state
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk 1f88866612 optimize api state extractor
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-31 03:04:17 +00:00
Jason Volk ccef1a4c8b add formal wrapping for api state
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-28 21:32:43 +00: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
243 changed files with 9942 additions and 6803 deletions
+243 -6
View File
@@ -27,6 +27,16 @@ concurrency:
cancel-in-progress: true
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
TERM: ansi
# Publishing to my nix binary cache
@@ -52,6 +62,8 @@ jobs:
tests:
name: Test
runs-on: ubuntu-latest
env:
CARGO_PROFILE: "test"
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
@@ -124,9 +136,33 @@ jobs:
- name: Cache CI dependencies
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
with:
cache-all-crates: "true"
- name: Run CI tests
run: |
@@ -180,6 +216,10 @@ jobs:
echo '```' >> $GITHUB_STEP_SUMMARY
fi
- name: Run cargo clean test artifacts
run: |
cargo clean --profile test
build:
name: Build
runs-on: ubuntu-latest
@@ -190,6 +230,9 @@ jobs:
- target: aarch64-unknown-linux-musl
- target: x86_64-unknown-linux-musl
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
- name: Sync repository
uses: actions/checkout@v4
@@ -244,12 +287,39 @@ jobs:
direnv allow
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 }}
run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
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/$CARGO_DEB_TARGET_TUPLE/release/
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/${{ 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 }}
uses: actions/upload-artifact@v4
with:
@@ -274,11 +404,65 @@ jobs:
if-no-files-found: error
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 }}
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
- 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 }}
uses: actions/upload-artifact@v4
with:
@@ -287,6 +471,14 @@ jobs:
if-no-files-found: error
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:
name: Docker publish
runs-on: ubuntu-latest
@@ -337,8 +529,10 @@ jobs:
- name: Move OCI images into position
run: |
mv -v oci-image-x86_64-*/*.tar.gz oci-image-amd64.tar.gz
mv -v oci-image-aarch64-*/*.tar.gz oci-image-arm64v8.tar.gz
mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.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
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
@@ -362,6 +556,28 @@ jobs:
docker push ${{ env.GHCR_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
run: |
# 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_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
# 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 }}
- 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
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
@@ -383,6 +611,12 @@ jobs:
docker manifest push ${{ env.GHCR_BRANCH }}
docker manifest push ${{ env.GLCR_TAG }}
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
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.GHCR_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 }}
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
- name: Sync repository
uses: actions/checkout@v4
@@ -107,7 +110,23 @@ jobs:
- name: Cache CI dependencies
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
run: |
@@ -116,8 +135,26 @@ jobs:
- name: Build documentation (book)
run: |
./bin/nix-build-and-cache just .#book
cp -r --dereference result public
# 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 .#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
uses: actions/upload-artifact@v4
with:
+1
View File
@@ -39,6 +39,7 @@ The following binaries are used in [`engage.toml`][engage.toml]:
- [`cargo-deb`][cargo-deb]
- [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli]
- `dpkg`
### Matrix tests
Generated
+562 -418
View File
File diff suppressed because it is too large Load Diff
+96 -40
View File
@@ -14,20 +14,36 @@ categories = ["network-programming"]
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
edition = "2021"
homepage = "https://conduwuit.puppyirl.gay/"
keywords = ["chat", "matrix", "server"]
keywords = ["chat", "matrix", "server", "uwu"]
license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://github.com/girlbossceo/conduwuit"
rust-version = "1.77.0"
version = "0.4.5"
rust-version = "1.80.1"
version = "0.4.6"
[workspace.metadata.crane]
name = "conduit"
[workspace.dependencies.arrayvec]
version = "0.7.4"
[workspace.dependencies.const-str]
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]
version = "0.5.0"
@@ -53,7 +69,7 @@ version = "0.8.5"
# Used for the http request / response body type for Ruma endpoints used with reqwest
[workspace.dependencies.bytes]
version = "1.6.1"
version = "1.7.1"
[workspace.dependencies.http-body-util]
version = "0.1.1"
@@ -62,7 +78,7 @@ version = "0.1.1"
version = "1.1.0"
[workspace.dependencies.regex]
version = "1.10.4"
version = "1.10.6"
[workspace.dependencies.axum]
version = "0.7.5"
@@ -83,18 +99,21 @@ default-features = false
features = ["typed-header", "tracing"]
[workspace.dependencies.axum-server]
version = "0.6.0"
version = "0.7.1"
default-features = false
features = ["tls-rustls"]
[workspace.dependencies.axum-client-ip]
version = "0.6.0"
[workspace.dependencies.tower]
version = "0.4.13"
version = "0.5.0"
default-features = false
features = ["util"]
[workspace.dependencies.tower-http]
version = "0.5.2"
default-features = false
features = [
"add-extension",
"cors",
@@ -117,10 +136,12 @@ features = [
[workspace.dependencies.serde]
version = "1.0.204"
default-features = false
features = ["rc"]
[workspace.dependencies.serde_json]
version = "1.0.120"
version = "1.0.124"
default-features = false
features = ["raw_value"]
# Used for appservice registration files
@@ -172,7 +193,7 @@ default-features = false
# used for conduit's CLI and admin room command parsing
[workspace.dependencies.clap]
version = "4.5.9"
version = "4.5.15"
default-features = false
features = [
"std",
@@ -188,7 +209,8 @@ version = "0.3.30"
default-features = false
[workspace.dependencies.tokio]
version = "1.38.0"
version = "1.39.2"
default-features = false
features = [
"fs",
"net",
@@ -204,11 +226,12 @@ features = [
version = "0.3.1"
[workspace.dependencies.libloading]
version = "0.8.3"
version = "0.8.5"
# Validating urls in config, was already a transitive dependency
[workspace.dependencies.url]
version = "2.5.0"
default-features = false
features = ["serde"]
# standard date and time tools
@@ -218,7 +241,8 @@ features = ["alloc", "std"]
default-features = false
[workspace.dependencies.hyper]
version = "1.4.0"
version = "1.4.1"
default-features = false
features = [
"server",
"http1",
@@ -227,6 +251,7 @@ features = [
[workspace.dependencies.hyper-util]
version = "0.1.6"
default-features = false
features = [
"client",
"server-auto",
@@ -238,11 +263,13 @@ features = [
# to support multiple variations of setting a config option
[workspace.dependencies.either]
version = "1.11.0"
default-features = false
features = ["serde"]
# Used for reading the configuration from conduwuit.toml & environment variables
[workspace.dependencies.figment]
version = "0.10.18"
default-features = false
features = ["env", "toml"]
[workspace.dependencies.hickory-resolver]
@@ -251,7 +278,7 @@ default-features = false
# Used for conduit::Error type
[workspace.dependencies.thiserror]
version = "1.0.62"
version = "1.0.63"
# Used when hashing the state
[workspace.dependencies.ring]
@@ -280,7 +307,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma"
#branch = "conduwuit-changes"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
rev = "11cbd52af1039f3d4a0b96333acd1d1687ba01aa"
features = [
"compat",
"rand",
@@ -293,6 +320,7 @@ features = [
"server-util",
"unstable-exhaustive-types",
"ring-compat",
"identifiers-validation",
"unstable-unspecified",
"unstable-msc2448",
"unstable-msc2666",
@@ -307,10 +335,6 @@ features = [
"unstable-extensible-events",
]
[workspace.dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruwuma"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
[workspace.dependencies.rust-rocksdb]
path = "deps/rust-rocksdb"
package = "rust-rocksdb-uwu"
@@ -372,24 +396,26 @@ version = "0.34.0"
version = "0.34.0"
# jemalloc usage
# locked to 0.5.4 due to static binary linking breakage
[workspace.dependencies.tikv-jemalloc-sys]
version = "0.5.4"
version = "=0.5.4"
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemallocator]
version = "0.5.4"
version = "=0.5.4"
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemalloc-ctl]
version = "0.5.4"
version = "=0.5.4"
default-features = false
features = ["use_std"]
[workspace.dependencies.console-subscriber]
version = "0.3"
version = "0.4"
[workspace.dependencies.nix]
version = "0.29.0"
default-features = false
features = ["resource"]
[workspace.dependencies.sd-notify]
@@ -405,16 +431,27 @@ features = [
]
[workspace.dependencies.rustyline-async]
version = "0.4.2"
version = "0.4.3"
default-features = false
[workspace.dependencies.termimad]
version = "0.29.4"
version = "0.30.0"
default-features = false
[workspace.dependencies.checked_ops]
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
@@ -436,17 +473,11 @@ rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
git = "https://github.com/girlbossceo/tracing"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
# fixes hyper graceful shutdowns [https://github.com/programatik29/axum-server/issues/114]
# https://github.com/girlbossceo/axum-server/commit/8e3368d899079818934e61cc9c839abcbbcada8a
[patch.crates-io.axum-server]
git = "https://github.com/girlbossceo/axum-server"
rev = "8e3368d899079818934e61cc9c839abcbbcada8a"
# adds a tab completion callback: https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\: https://github.com/girlbossceo/rustyline-async/commit/67d8c49aeac03a5ef4e818f663eaa94dd7bf339b
[patch.crates-io.rustyline-async]
git = "https://github.com/girlbossceo/rustyline-async"
rev = "de26100b0db03e419a3d8e1dd26895d170d1fe50"
rev = "9654cc84e19241f6e19021eb8e677892656f5071"
#
# Our crates
@@ -482,6 +513,11 @@ package = "conduit_core"
path = "src/core"
default-features = false
[workspace.dependencies.conduit-macros]
package = "conduit_macros"
path = "src/macros"
default-features = false
###############################################################################
#
# Release profiles
@@ -530,7 +566,17 @@ lto = "fat"
[profile.release-max-perf.build-override]
inherits = "release-max-perf"
opt-level = 0
codegen-units = 32
#rustflags = [
# '-Crelocation-model=pic',
# '-Ctarget-feature=-crt-static',
# '-Clink-arg=-Wl,--no-gc-sections',
#]
[profile.release-max-perf.package.conduit_macros]
inherits = "release-max-perf.build-override"
#rustflags = [
# '-Crelocation-model=pic',
# '-Ctarget-feature=-crt-static',
#]
@@ -659,7 +705,16 @@ opt-level = 'z'
# '-Clink-arg=-Wl,-z,nodelete',
#]
# primarily used for CI
[profile.test]
inherits = "dev"
codegen-units = 16
incremental = false
[profile.test.package.'*']
inherits = "dev"
debug = 0
codegen-units = 16
incremental = false
###############################################################################
@@ -721,19 +776,19 @@ variant_size_differences = "allow"
[workspace.lints.clippy]
###################
cargo = "warn"
cargo = { level = "warn", priority = -1 }
## some sadness
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
missing_const_for_fn = { level = "allow", priority = 1 } # TODO
@@ -743,7 +798,7 @@ significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
significant_drop_tightening = { level = "allow", priority = 1 } # TODO
###################
pedantic = "warn"
pedantic = { level = "warn", priority = -1 }
## some sadness
doc_markdown = { level = "allow", priority = 1 }
@@ -762,12 +817,12 @@ unnecessary_wraps = { level = "allow", priority = 1 }
unused_async = { level = "allow", priority = 1 }
###################
perf = "warn"
perf = { level = "warn", priority = -1 }
###################
#restriction = "warn"
allow_attributes = "warn"
#allow_attributes = "warn" # UNSTABLE
arithmetic_side_effects = "warn"
as_conversions = "warn"
as_underscore = "warn"
@@ -812,14 +867,15 @@ unseparated_literal_suffix = "warn"
verbose_file_reads = "warn"
###################
style = "warn"
style = { level = "warn", priority = -1 }
## some sadness
# trivial assertions are quite alright
assertions_on_constants = { level = "allow", priority = 1 }
module_inception = { level = "allow", priority = 1 }
###################
suspicious = "warn"
suspicious = { level = "warn", priority = -1 }
## some sadness
let_underscore_future = { level = "allow", priority = 1 }
+34 -6
View File
@@ -52,9 +52,10 @@
# 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
# 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.
# 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.
# Basically "global" ACLs. For our user (client) checks, admin users are allowed.
# No default.
# List of forbidden server names that we will block incoming AND outgoing federation with, and block client room joins / remote user invites.
#
# 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 = []
# 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.
#
# Enabled by default.
#media_statup_check = true
#media_startup_check = true
# OpenID token expiration/TTL in seconds
#
@@ -513,6 +516,31 @@ allow_profile_lookup_federation_requests = true
# Defaults to false as this uses more CPU when compressing.
#rocksdb_bottommost_compression = false
# Level of statistics collection. Some admin commands to display database statistics may require
# this option to be set. Database performance may be impacted by higher settings.
#
# Option is a number ranging from 0 to 6:
# 0 = No statistics.
# 1 = No statistics in release mode (default).
# 2 to 3 = Statistics with no performance impact.
# 3 to 5 = Statistics with possible performance impact.
# 6 = All statistics.
#
# Defaults to 1 (No statistics, except in debug-mode)
#rocksdb_stats_level = 1
# Database repair mode (for RocksDB SST corruption)
#
# Use this option when the server reports corruption while running or panics. If the server refuses
# to start use the recovery mode options first. Corruption errors containing the acronym 'SST' which
# occur after startup will likely require this option.
#
# - Backing up your database directory is recommended prior to running the repair.
# - Disabling repair mode and restarting the server is recommended after running the repair.
#
# Defaults to false
#rocksdb_repair = false
# Database recovery mode (for RocksDB WAL corruption)
#
# Use this option when the server reports corruption and refuses to start. Set mode 2 (PointInTime)
-1
View File
@@ -16,7 +16,6 @@ case "$1" in
--home "$CONDUWUIT_DATABASE_PATH" \
--disabled-login \
--shell "/usr/sbin/nologin" \
--verbose \
conduwuit
fi
+1
View File
@@ -10,6 +10,7 @@
- [Docker](deploying/docker.md)
- [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md)
- [FreeBSD](deploying/freebsd.md)
- [TURN](turn.md)
- [Appservices](appservices.md)
- [Maintenance](maintenance.md)
+24 -4
View File
@@ -4,15 +4,35 @@ This chapter describes various ways to configure conduwuit.
## Basics
Conduwuit uses a config file for the majority of the settings. Please refer to the
[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
conduwuit uses a config file for the majority of the settings, but also supports setting individual config options via commandline.
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
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
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_`.
For example, if the setting you are changing is `max_request_size`, then the environment variable to set is
`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`).
+2 -2
View File
@@ -17,9 +17,9 @@ OCI images for conduwuit are available in the registries listed below.
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
| Docker Hub | [docker.io/girlbossceo/conduwuit:main][dh] | ![Image Size][shield-main] | Stable main branch. |
[dh]: https://hub.docker.com/repository/docker/girlbossceo/conduwuit
[dh]: https://hub.docker.com/r/girlbossceo/conduwuit
[gh]: https://github.com/girlbossceo/conduwuit/pkgs/container/conduwuit
[gl]: https://gitlab.com/conduwuit/conduwuit/container_registry/6351657
[gl]: https://gitlab.com/conduwuit/conduwuit/container_registry/6369729
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
+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.
Prebuilt binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest).
The latest tagged release also includes the Debian packages.
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.
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.
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
+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.
## 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
#### 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
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
## Media
## 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`.
+18 -3
View File
@@ -60,6 +60,11 @@ name = "markdownlint"
group = "versions"
script = "markdownlint --version"
[[task]]
name = "dpkg"
group = "versions"
script = "dpkg --version"
[[task]]
name = "cargo-audit"
group = "security"
@@ -68,7 +73,9 @@ script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
[[task]]
name = "cargo-fmt"
group = "lints"
script = "cargo fmt --check -- --color=always"
script = """
cargo fmt --check -- --color=always
"""
[[task]]
name = "cargo-doc"
@@ -79,6 +86,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo doc \
--workspace \
--profile test \
--all-features \
--no-deps \
--document-private-items \
@@ -91,6 +99,7 @@ group = "lints"
script = """
cargo clippy \
--workspace \
--profile test \
--all-targets \
--color=always \
-- \
@@ -105,6 +114,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo clippy \
--workspace \
--profile test \
--all-targets \
--all-features \
--color=always \
@@ -118,6 +128,7 @@ group = "lints"
script = """
cargo clippy \
--workspace \
--profile test \
--features jemalloc \
--all-targets \
--color=always \
@@ -156,6 +167,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo test \
--workspace \
--profile test \
--all-targets \
--all-features \
--color=always \
@@ -169,6 +181,7 @@ group = "tests"
script = """
cargo test \
--workspace \
--profile test \
--all-targets \
--color=always \
-- \
@@ -185,9 +198,11 @@ name = "nix-default"
group = "tests"
script = """
env DIRENV_DEVSHELL=dynamic \
CARGO_PROFILE="test" \
direnv exec . \
bin/nix-build-and-cache just .#default
bin/nix-build-and-cache just .#default-test
env DIRENV_DEVSHELL=dynamic \
CARGO_PROFILE="test" \
direnv exec . \
nix run -L .#default -- --help
nix run -L .#default-test -- --help && nix run -L .#default-test -- --version
"""
Generated
+66 -79
View File
@@ -9,11 +9,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1720542474,
"narHash": "sha256-aKjJ/4l2I9+wNGTaOGRsuS3M1+IoTibqgEMPDikXm04=",
"lastModified": 1723558887,
"narHash": "sha256-FBaRmikNnCX+HQJXAfNJyiOl3sM1NrlxvcFwQDqwCyk=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "6139576a3ce6bb992e0f6c3022528ec233e45f00",
"rev": "26b9417bde03edc6280d1f7ce709cd619cdb72d4",
"type": "github"
},
"original": {
@@ -27,15 +27,15 @@
"inputs": {
"devenv": "devenv",
"flake-compat": "flake-compat_3",
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": "pre-commit-hooks"
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1719923519,
"narHash": "sha256-7Rhljj2fsklFRsu+eq7N683Z9qukmreMEj5C1GqCrSA=",
"lastModified": 1723252655,
"narHash": "sha256-uOxcrNEMTOHvGHYyqDWnUpkhkY29l01KhjkgVQU2gXg=",
"owner": "cachix",
"repo": "cachix",
"rev": "4e9e71f78b9500fa6210cf1eaa4d75bdbab777c3",
"rev": "bd558fd32f55d6df1c3d496bade90335a1a6044d",
"type": "github"
},
"original": {
@@ -81,11 +81,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1720637557,
"narHash": "sha256-oZz6nCmFmdJZpC+K1iOG2KkzTI6rlAmndxANPDVU7X0=",
"lastModified": 1722323564,
"narHash": "sha256-6w6/N8walz4Ayc9zu7iySqJRmGFukhkaICLn4dweAcA=",
"owner": "matrix-org",
"repo": "complement",
"rev": "0d14432e010482ea9e13a6f7c47c1533c0c9d62f",
"rev": "6e4426a9e63233f9821a4d2382bfed145244183f",
"type": "github"
},
"original": {
@@ -123,11 +123,11 @@
]
},
"locked": {
"lastModified": 1720546058,
"narHash": "sha256-iU2yVaPIZm5vMGdlT0+57vdB/aPq/V5oZFBRwYw+HBM=",
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"owner": "ipetkov",
"repo": "crane",
"rev": "2d83156f23c43598cf44e152c33a59d3892f8b29",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"type": "github"
},
"original": {
@@ -151,15 +151,15 @@
],
"pre-commit-hooks": [
"cachix",
"pre-commit-hooks"
"git-hooks"
]
},
"locked": {
"lastModified": 1719759336,
"narHash": "sha256-3a34VL/QnHprl5gMy9xlx6d8J+iNp+W88Ex8smkgH9M=",
"lastModified": 1723156315,
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
"owner": "cachix",
"repo": "devenv",
"rev": "bb32aa986f2f695385e54428d0eaf7d05b31466e",
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
"type": "github"
},
"original": {
@@ -209,11 +209,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1720852044,
"narHash": "sha256-3NBYz8VuXuKU+8ONd9NFafCNjPEGHIZQ2Mdoam1a4mY=",
"lastModified": 1723530607,
"narHash": "sha256-FaXZZLLDW1D+pj7UgrIslDS8XjMMG3Pus5gAvUYWQS0=",
"owner": "nix-community",
"repo": "fenix",
"rev": "5087b12a595ee73131a944d922f24d81dae05725",
"rev": "296d44c440302980824c5f3b67e477cf0522e0c1",
"type": "github"
},
"original": {
@@ -272,22 +272,6 @@
}
},
"flake-compat_4": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_5": {
"flake": false,
"locked": {
"lastModified": 1696426674,
@@ -356,11 +340,38 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"cachix",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"cachix",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1723202784,
"narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "c7012d0c18567c889b948781bc74a501e92275d1",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"cachix",
"pre-commit-hooks",
"git-hooks",
"nixpkgs"
]
},
@@ -381,11 +392,11 @@
"liburing": {
"flake": false,
"locked": {
"lastModified": 1720798442,
"narHash": "sha256-gtPppAoksMLW4GuruQ36nf4EAqIA1Bs6V9Xcx8dBxrQ=",
"lastModified": 1723553052,
"narHash": "sha256-sjqmYP/tvQKmsTD6FOd8m7aEja2hhEyyB3aTxVXEbuE=",
"owner": "axboe",
"repo": "liburing",
"rev": "1d674f83b7d0f07553ac44d99a401b05853d9dbe",
"rev": "401b3e4bde2316d35e93b548269c72bbb64adc02",
"type": "github"
},
"original": {
@@ -558,16 +569,16 @@
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1718811006,
"narHash": "sha256-0Y8IrGhRmBmT7HHXlxxepg2t8j1X90++qRN3lukGaIk=",
"lastModified": 1720386169,
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "03d771e513ce90147b65fe922d87d3a0356fc125",
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
@@ -590,11 +601,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1719848872,
"narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=",
"lastModified": 1722813957,
"narHash": "sha256-IAoYyYnED7P8zrBFMnmp7ydaJfwTnwcnqxUElC1I26Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8",
"rev": "cb9a96f23c491c081b38eab96d22fa958043c9fa",
"type": "github"
},
"original": {
@@ -606,11 +617,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1720768451,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
"lastModified": 1723362943,
"narHash": "sha256-dFZRVSgmJkyM0bkPpaYRtG/kRMRTorUIDj8BxoOt1T4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
"rev": "a58bc8ad779655e790115244571758e8de055e3d",
"type": "github"
},
"original": {
@@ -646,30 +657,6 @@
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_4",
"gitignore": "gitignore",
"nixpkgs": [
"cachix",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1719259945,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
@@ -694,7 +681,7 @@
"complement": "complement",
"crane": "crane_2",
"fenix": "fenix",
"flake-compat": "flake-compat_5",
"flake-compat": "flake-compat_4",
"flake-utils": "flake-utils_3",
"liburing": "liburing",
"nix-filter": "nix-filter",
@@ -705,11 +692,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1720717809,
"narHash": "sha256-6I+fm+nTLF/iaj7ffiFGlSY7POmubwUaPA/Wq0Bm53M=",
"lastModified": 1723473250,
"narHash": "sha256-Ls0e6R4FmGUFXZlUcm6ZQaVNJ4Yj/nua4SSctXIopao=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "ffbc5ad993d5cd2f3b8bcf9a511165470944ab91",
"rev": "32a86cb1dad2b208e8f36f1bb50c2e4806b0371f",
"type": "github"
},
"original": {
+113 -1
View File
@@ -24,7 +24,7 @@
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8";
sha256 = "sha256-3jVIIf5XPnUU1CRaTyAiO0XHVbJl12MSx3eucTXCjtE=";
};
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -119,6 +119,9 @@
# Needed for producing Debian packages
cargo-deb
# Needed for CI to check validity of produced Debian packages (dpkg-deb)
dpkg
# Needed for Complement
go
@@ -133,6 +136,12 @@
# Useful for editing the book locally
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.propagatedBuildInputs
@@ -144,6 +153,15 @@
{
packages = {
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 = true;
disable_features = [
@@ -153,6 +171,18 @@
"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"]; };
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 {
main = scopeHost.main.override {
features = ["hardened_malloc"];
@@ -201,6 +245,26 @@
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`
{
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
{
name = "${binaryName}-hmalloc";
@@ -229,6 +310,18 @@
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`
{
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
{
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 = []
url_preview_domain_contains_allowlist = ["*"]
media_compat_file_link = false
media_statup_check = false
media_startup_check = false
rocksdb_direct_io = false
[global.tls]
+9 -2
View File
@@ -14,8 +14,15 @@
let
main' = main.override {
profile = "dev";
features = ["axum_dual_protocol"];
profile = "test";
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" ''
+20 -4
View File
@@ -7,6 +7,7 @@
, liburing
, pkgsBuildHost
, rocksdb
, removeReferencesTo
, rust
, rust-jemalloc-sys
, stdenv
@@ -51,6 +52,10 @@ rust-jemalloc-sys' = (rust-jemalloc-sys.override {
unprefixed = true;
}).overrideAttrs (old: {
configureFlags = old.configureFlags ++
# we dont need docs
[ "--disable-doc" ] ++
# we dont need cxx/C++ integration
[ "--disable-cxx" ] ++
# tikv-jemalloc-sys/profiling feature
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
});
@@ -90,8 +95,8 @@ buildDepsOnlyEnv =
else if stdenv.targetPlatform.isAarch64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isAarch64 [
# cortex-a55 == ARMv8.2-a
"-DPORTABLE=armv8.2-a"
# cortex-a73 == ARMv8-A
"-DPORTABLE=armv8-a"
]
else old.cmakeFlags;
});
@@ -124,7 +129,7 @@ buildPackageEnv = {
+ lib.optionalString stdenv.targetPlatform.isx86_64
" -Ctarget-cpu=x86-64-v2"
+ 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';
@@ -164,6 +170,9 @@ commonAttrs = {
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents.
jq
# needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo
]
++ lib.optionals stdenv.isDarwin [
# 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
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
+1 -1
View File
@@ -11,7 +11,7 @@
# If you're having trouble making the relevant changes, bug a maintainer.
[toolchain]
channel = "1.77.0"
channel = "1.80.1"
components = [
# For rust-analyzer
"rust-src",
+1
View File
@@ -29,6 +29,7 @@ release_max_log_level = [
clap.workspace = true
conduit-api.workspace = true
conduit-core.workspace = true
conduit-macros.workspace = true
conduit-service.workspace = true
const-str.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 crate::{services, Result};
use crate::{admin_command, Result};
pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"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);
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!(
"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> {
match services()
#[admin_command]
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice
.unregister_appservice(&appservice_identifier)
.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> {
match services()
#[admin_command]
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice
.get_registration(&appservice_identifier)
.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> {
let appservices = services().appservice.iter_ids().await;
#[admin_command]
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(", "));
Ok(RoomMessageEventContent::text_plain(output))
}
+7 -19
View File
@@ -2,12 +2,11 @@ mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*;
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
#[derive(Debug, Subcommand)]
#[admin_command_dispatch]
pub(super) enum AppserviceCommand {
/// - Register an appservice using its registration YAML
///
@@ -29,24 +28,13 @@ pub(super) enum AppserviceCommand {
/// - Show an appservice's config using its ID
///
/// You can find the ID using the `list-appservices` command.
Show {
#[clap(alias("show"))]
ShowAppserviceConfig {
/// The appservice to show
appservice_identifier: String,
},
/// - List all the currently registered appservices
List,
}
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?,
})
#[clap(alias("list"))]
ListRegistered,
}
+5 -3
View File
@@ -1,14 +1,16 @@
use conduit::Result;
use conduit_macros::implement;
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
/// every user in our database (remote and local). Reports total count, any
/// 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 results = services().users.db.iter();
let results = self.services.users.db.iter();
let query_time = timer.elapsed();
let users = results.collect::<Vec<_>>();
+4 -5
View File
@@ -4,16 +4,15 @@ use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*;
use crate::Command;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
#[derive(Debug, Subcommand)]
pub(super) enum CheckCommand {
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 {
CheckCommand::AllUsers => check_all_users(body).await?,
CheckCommand::AllUsers => context.check_all_users().await?,
})
}
+9
View File
@@ -0,0 +1,9 @@
use std::time::SystemTime;
use conduit_service::Services;
pub(crate) struct Command<'a> {
pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str],
pub(crate) timer: SystemTime,
}
+275 -133
View File
@@ -1,33 +1,33 @@
use std::{
collections::{BTreeMap, HashMap},
sync::{Arc, Mutex},
time::Instant,
fmt::Write,
sync::Arc,
time::{Instant, SystemTime},
};
use api::client::validate_and_add_event_id;
use conduit::{
debug, info, log,
log::{capture, Capture},
warn, Error, Result,
};
use conduit::{debug, debug_error, err, info, trace, utils, warn, Error, PduEvent, Result};
use ruma::{
api::{client::error::ErrorKind, federation::event::get_room_state},
events::room::message::RoomMessageEventContent,
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 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(" ");
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);
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
.get("room_id")
.and_then(|val| val.as_str())
@@ -35,13 +35,16 @@ pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
let room_id = <&RoomId>::try_from(room_id_str)
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
let start = Instant::now();
let count = services()
let count = self
.services
.rooms
.auth_chain
.event_ids_iter(room_id, vec![event_id])
.await?
.count();
let elapsed = start.elapsed();
Ok(RoomMessageEventContent::text_plain(format!(
"Loaded auth chain with length {count} in {elapsed:?}"
@@ -51,14 +54,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> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"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) {
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
Ok(hash) => {
@@ -79,15 +84,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 pdu_json = services()
let mut pdu_json = self
.services
.rooms
.timeline
.get_non_outlier_pdu_json(&event_id)?;
if pdu_json.is_none() {
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 {
Some(json) => {
@@ -106,69 +113,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(
body: Vec<&str>, server: Box<ServerName>, force: bool,
&self, server: Box<ServerName>, force: bool,
) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.",
));
}
if server == services().globals.server_name() {
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
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(
"Expected code block in command body. Add --help for details.",
));
}
let list = body
.clone()
.drain(1..body.len().checked_sub(1).unwrap())
let list = self
.body
.iter()
.collect::<Vec<_>>()
.drain(1..self.body.len().saturating_sub(1))
.filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>();
let mut failed_count: usize = 0;
let mut success_count: usize = 0;
for pdu in list {
if force {
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
services()
if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await {
failed_count = failed_count.saturating_add(1);
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Failed to get remote PDU, ignoring error: {e}"
)))
.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 {
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(
_body: Vec<&str>, event_id: Box<EventId>, server: Box<ServerName>,
&self, event_id: Box<EventId>, server: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.",
));
}
if server == services().globals.server_name() {
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
));
}
match services()
match self
.services
.sending
.send_federation_request(
&server,
@@ -187,9 +209,13 @@ pub(super) async fn get_remote_pdu(
Error::BadRequest(ErrorKind::Unknown, "Received response from server but failed to parse PDU")
})?;
debug!("Attempting to parse PDU: {:?}", &response.pdu);
trace!("Attempting to parse PDU: {:?}", &response.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 {
Ok(t) => t,
Err(e) => {
@@ -207,9 +233,8 @@ pub(super) async fn get_remote_pdu(
let pub_key_map = RwLock::new(BTreeMap::new());
debug!("Attempting to fetch homeserver signing keys for {server}");
services()
.rooms
.event_handler
self.services
.server_keys
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
.await
.unwrap_or_else(|e| {
@@ -217,7 +242,7 @@ pub(super) async fn get_remote_pdu(
});
info!("Attempting to handle event ID {event_id} as backfilled PDU");
services()
self.services
.rooms
.timeline
.backfill_pdu(&server, response.pdu, &pub_key_map)
@@ -236,9 +261,11 @@ pub(super) async fn get_remote_pdu(
}
}
pub(super) async fn get_room_state(_body: Vec<&str>, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_id = services().rooms.alias.resolve(&room).await?;
let room_state = services()
#[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state = self
.services
.rooms
.state_accessor
.room_state_full(&room_id)
@@ -263,8 +290,9 @@ pub(super) async fn get_room_state(_body: Vec<&str>, room: OwnedRoomOrAliasId) -
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
}
pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
if server == services().globals.server_name() {
#[admin_command]
pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves.",
));
@@ -272,7 +300,8 @@ pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
let timer = tokio::time::Instant::now();
match services()
match self
.services
.sending
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.await
@@ -301,23 +330,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
for user_id in services().users.iter().filter_map(Result::ok) {
services().users.mark_device_key_update(&user_id)?;
for user_id in self.services.users.iter().filter_map(Result::ok) {
self.services.users.mark_device_key_update(&user_id)?;
}
Ok(RoomMessageEventContent::text_plain(
"Marked all devices for all users as having new keys to update",
))
}
pub(super) async fn change_log_level(
_body: Vec<&str>, filter: Option<String>, reset: bool,
) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
let handles = &["console"];
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,
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
@@ -326,7 +355,8 @@ pub(super) async fn change_log_level(
},
};
match services()
match self
.services
.server
.log
.reload
@@ -335,7 +365,7 @@ pub(super) async fn change_log_level(
Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}",
services().globals.config.log
self.services.globals.config.log
)));
},
Err(e) => {
@@ -356,7 +386,8 @@ pub(super) async fn change_log_level(
},
};
match services()
match self
.services
.server
.log
.reload
@@ -376,19 +407,21 @@ pub(super) async fn change_log_level(
Ok(RoomMessageEventContent::text_plain("No log level was specified."))
}
pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"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) {
Ok(mut value) => {
ruma::signatures::sign_json(
services().globals.server_name().as_str(),
services().globals.keypair(),
self.services.globals.server_name().as_str(),
self.services.globals.keypair(),
&mut value,
)
.expect("our request json is what ruma expects");
@@ -399,21 +432,22 @@ pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent
}
}
pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"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) {
Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new());
services()
.rooms
.event_handler
self.services
.server_keys
.fetch_required_signing_keys([&value], &pub_key_map)
.await?;
@@ -429,19 +463,22 @@ pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventConte
}
}
#[tracing::instrument(skip(_body))]
pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !services()
#[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms
.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(
"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
.timeline
.first_pdu_in_room(&room_id)?
@@ -450,19 +487,22 @@ pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) ->
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
}
#[tracing::instrument(skip(_body))]
pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !services()
#[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms
.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(
"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
.timeline
.latest_pdu_in_room(&room_id)?
@@ -471,32 +511,36 @@ pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -
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(
_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>,
&self, room_id: Box<RoomId>, server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !services()
if !self
.services
.rooms
.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(
"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
.timeline
.latest_pdu_in_room(&room_id)?
.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 pub_key_map = RwLock::new(BTreeMap::new());
let remote_state_response = services()
let remote_state_response = self
.services
.sending
.send_federation_request(
&server_name,
@@ -510,7 +554,7 @@ pub(super) async fn force_set_room_state_from_server(
let mut events = Vec::with_capacity(remote_state_response.pdus.len());
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,
Err(e) => {
warn!("Could not parse PDU, ignoring: {e}");
@@ -520,9 +564,8 @@ pub(super) async fn force_set_room_state_from_server(
}
info!("Fetching required signing keys for all the state events we got");
services()
.rooms
.event_handler
self.services
.server_keys
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
.await?;
@@ -530,23 +573,24 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response
.pdus
.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 {
continue;
};
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
warn!("Invalid PDU in fetching remote room state PDUs response: {} {:?}", e, value);
Error::BadServerResponse("Invalid PDU in send_join response.")
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
})?;
services()
self.services
.rooms
.outlier
.add_pdu_outlier(&event_id, &value)?;
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services()
let shortstatekey = self
.services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
@@ -558,32 +602,34 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response
.auth_chain
.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 {
continue;
};
services()
self.services
.rooms
.outlier
.add_pdu_outlier(&event_id, &value)?;
}
let new_room_state = services()
let new_room_state = self
.services
.rooms
.event_handler
.resolve_state(room_id.clone().as_ref(), &room_version, state)
.await?;
info!("Forcing new room state");
let (short_state_hash, new, removed) = services()
let (short_state_hash, new, removed) = self
.services
.rooms
.state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)?;
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
services()
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
self.services
.rooms
.state
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
@@ -593,7 +639,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 \
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);
@@ -602,61 +651,99 @@ pub(super) async fn force_set_room_state_from_server(
))
}
pub(super) async fn resolve_true_destination(
_body: Vec<&str>, server_name: Box<ServerName>, no_cache: bool,
#[admin_command]
pub(super) async fn get_signing_keys(
&self, server_name: Option<Box<ServerName>>, _cached: bool,
) -> 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
.server_keys
.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(
"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(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
));
}
let filter: &capture::Filter = &|data| {
data.level() <= log::Level::DEBUG
&& data.mod_name().starts_with("conduit")
&& matches!(data.span_name(), "actual" | "well-known" | "srv")
};
let actual = self
.services
.resolver
.resolve_actual_dest(&server_name, !no_cache)
.await?;
let state = &services().server.log.capture;
let logs = Arc::new(Mutex::new(String::new()));
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
let msg = format!("Destination: {}\nHostname URI: {}", actual.dest, actual.host,);
let capture_scope = capture.start();
let actual = resolve_actual_dest(&server_name, !no_cache).await?;
drop(capture_scope);
let msg = format!(
"{}\nDestination: {}\nHostname URI: {}",
logs.lock().expect("locked"),
actual.dest,
actual.host,
);
Ok(RoomMessageEventContent::text_markdown(msg))
}
#[must_use]
pub(super) fn memory_stats() -> RoomMessageEventContent {
#[admin_command]
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
let html_body = conduit::alloc::memory_stats();
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(),
html_body.expect("string result"),
)
))
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_metrics().map_or_else(
#[admin_command]
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(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
@@ -665,15 +752,17 @@ pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEvent
}
#[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(
"Runtime metrics require building with `tokio_unstable`.",
))
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_interval().map_or_else(
#[admin_command]
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(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
@@ -682,8 +771,61 @@ pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEven
}
#[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(
"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))
}
#[admin_command]
pub(super) async fn database_stats(
&self, property: Option<String>, map: Option<String>,
) -> Result<RoomMessageEventContent> {
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
let map_name = map.as_ref().map_or(utils::string::EMPTY, String::as_str);
let mut out = String::new();
for (name, map) in self.services.db.iter_maps() {
if !map_name.is_empty() && *map_name != *name {
continue;
}
let res = map.property(&property)?;
let res = res.trim();
writeln!(out, "##### {name}:\n```\n{res}\n```")?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
}
+31 -59
View File
@@ -3,13 +3,13 @@ pub(crate) mod tester;
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use tester::TesterCommand;
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use self::commands::*;
use self::tester::TesterCommand;
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum DebugCommand {
/// - Echo input of admin command
Echo {
@@ -76,6 +76,14 @@ pub(super) enum DebugCommand {
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
/// `/_matrix/federation/v1/version` endpoint and measures the latency it
/// took for the server to respond
@@ -167,61 +175,25 @@ pub(super) enum DebugCommand {
/// invocation.
RuntimeInterval,
/// - Print the current time
Time,
/// - List dependencies
ListDependencies {
#[arg(short, long)]
names: bool,
},
/// - Get database statistics
DatabaseStats {
property: Option<String>,
#[arg(short, long, alias("column"))]
map: Option<String>,
},
/// - Developer test stubs
#[command(subcommand)]
#[allow(non_snake_case)]
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 crate::Result;
use crate::{admin_command, admin_command_dispatch, Result};
#[derive(clap::Subcommand)]
#[cfg_attr(test, derive(Debug))]
#[admin_command_dispatch]
#[derive(Debug, clap::Subcommand)]
pub(crate) enum TesterCommand {
Tester,
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)]
#[rustfmt::skip]
#[allow(unused_variables)]
async fn tester(body: Vec<&str>) -> Result<RoomMessageEventContent> {
#[admin_command]
async fn tester(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::notice_plain("completed"))
}
#[inline(never)]
#[rustfmt::skip]
async fn timer(body: Vec<&str>) -> Result<RoomMessageEventContent> {
#[admin_command]
async fn timer(&self) -> Result<RoomMessageEventContent> {
let started = std::time::Instant::now();
timed(&body);
timed(self.body);
let elapsed = started.elapsed();
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))
+23 -17
View File
@@ -1,21 +1,26 @@
use std::fmt::Write;
use conduit::Result;
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> {
services().rooms.metadata.disable_room(&room_id, true)?;
#[admin_command]
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."))
}
pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
services().rooms.metadata.disable_room(&room_id, false)?;
#[admin_command]
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."))
}
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let map = services()
#[admin_command]
pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> {
let map = self
.services
.rooms
.event_handler
.federation_handletime
@@ -31,11 +36,10 @@ pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageE
Ok(RoomMessageEventContent::text_plain(&msg))
}
pub(super) async fn fetch_support_well_known(
_body: Vec<&str>, server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
let response = services()
.globals
#[admin_command]
pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
let response = self
.services
.client
.default
.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> {
if user_id.server_name() == services().globals.config.server_name {
#[admin_command]
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(
"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(
"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
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.map(|room_id| get_room_info(&room_id))
.map(|room_id| get_room_info(self.services, &room_id))
.collect();
if rooms.is_empty() {
+4 -22
View File
@@ -2,12 +2,12 @@ mod commands;
use clap::Subcommand;
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))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum FederationCommand {
/// - List all rooms we are currently handling an incoming pdu from
IncomingFederation,
@@ -40,21 +40,3 @@ pub(super) enum FederationCommand {
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?,
})
}
+97 -108
View File
@@ -1,7 +1,21 @@
use std::{panic::AssertUnwindSafe, time::Instant};
use std::{
panic::AssertUnwindSafe,
sync::{Arc, Mutex},
time::SystemTime,
};
use clap::{CommandFactory, Parser};
use conduit::{error, trace, Error};
use conduit::{
debug, error,
log::{
capture,
capture::Capture,
fmt::{markdown_table, markdown_table_head},
},
trace,
utils::string::{collect_stream, common_prefix},
Error, Result,
};
use futures_util::future::FutureExt;
use ruma::{
events::{
@@ -10,82 +24,49 @@ use ruma::{
},
OwnedEventId,
};
extern crate conduit_service as service;
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,
use service::{
admin::{CommandInput, CommandOutput, HandlerFuture, HandlerResult},
Services,
};
pub(crate) const PAGE_SIZE: usize = 100;
use tracing::Level;
#[derive(Parser)]
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
Appservices(AppserviceCommand),
use crate::{admin, admin::AdminCommand, Command};
#[command(subcommand)]
/// - Commands for managing local users
Users(UserCommand),
#[must_use]
pub(super) fn complete(line: &str) -> String { complete_command(AdminCommand::command(), line) }
#[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),
#[must_use]
pub(super) fn handle(services: Arc<Services>, command: CommandInput) -> HandlerFuture {
Box::pin(handle_command(services, command))
}
#[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")]
async fn handle_command(command: Command) -> CommandResult {
AssertUnwindSafe(process_command(&command))
async fn handle_command(services: Arc<Services>, command: CommandInput) -> HandlerResult {
AssertUnwindSafe(Box::pin(process_command(services, &command)))
.catch_unwind()
.await
.map_err(Error::from_panic)
.or_else(|error| handle_panic(&error, command))
}
async fn process_command(command: &Command) -> CommandOutput {
process_admin_message(&command.command)
async fn process_command(services: Arc<Services>, input: &CommandInput) -> CommandOutput {
let (command, args, body) = match parse(&services, input) {
Err(error) => return error,
Ok(parsed) => parsed,
};
let context = Command {
services: &services,
body: &body,
timer: SystemTime::now(),
};
process(&context, command, &args)
.await
.and_then(|content| reply(content, command.reply_id.clone()))
.and_then(|content| reply(content, input.reply_id.clone()))
}
fn handle_panic(error: &Error, command: Command) -> CommandResult {
fn handle_panic(error: &Error, command: CommandInput) -> HandlerResult {
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 content = RoomMessageEventContent::notice_markdown(msg);
@@ -104,56 +85,65 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -
}
// Parse and process a message from the admin room
async fn process_admin_message(msg: &str) -> CommandOutput {
let mut lines = msg.lines().filter(|l| !l.trim().is_empty());
let command = lines.next().expect("each string has at least one line");
let body = lines.collect::<Vec<_>>();
let parsed = match parse_admin_command(command) {
Ok(parsed) => parsed,
Err(error) => {
let server_name = services().globals.server_name();
let message = error.replace("server.name", server_name.as_str());
return Some(RoomMessageEventContent::notice_markdown(message));
},
async fn process(context: &Command<'_>, command: AdminCommand, args: &[String]) -> CommandOutput {
let filter: &capture::Filter =
&|data| data.level() <= Level::DEBUG && data.our_modules() && data.scope.contains(&"admin");
let logs = Arc::new(Mutex::new(
collect_stream(|s| markdown_table_head(s)).expect("markdown table header"),
));
let capture = Capture::new(
&context.services.server.log.capture,
Some(filter),
capture::fmt(markdown_table, logs.clone()),
);
let capture_scope = capture.start();
let result = Box::pin(admin::process(command, context)).await;
drop(capture_scope);
debug!(
ok = result.is_ok(),
elapsed = ?context.timer.elapsed(),
command = ?args,
"command processed"
);
let logs = logs.lock().expect("locked");
let output = match result {
Err(error) => format!("{logs}\nEncountered an error while handling the command:\n```\n{error:#?}\n```"),
Ok(reply) => format!("{logs}\n{}", reply.body()), //TODO: content is recreated to add logs
};
let timer = Instant::now();
let result = process_admin_command(parsed, body).await;
let elapsed = timer.elapsed();
conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}");
match result {
Ok(reply) => Some(reply),
Err(error) => Some(RoomMessageEventContent::notice_markdown(format!(
"Encountered an error while handling the command:\n```\n{error:#?}\n```"
))),
}
}
#[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)
Some(RoomMessageEventContent::notice_markdown(output))
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
let argv = parse_command_line(command_line);
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
fn parse<'a>(
services: &Arc<Services>, input: &'a CommandInput,
) -> Result<(AdminCommand, Vec<String>, Vec<&'a str>), CommandOutput> {
let lines = input.command.lines().filter(|line| !line.trim().is_empty());
let command_line = lines.clone().next().expect("command missing first line");
let body = lines.skip(1).collect();
match parse_command(command_line) {
Ok((command, args)) => Ok((command, args, body)),
Err(error) => {
let message = error
.to_string()
.replace("server.name", services.globals.server_name().as_str());
Err(Some(RoomMessageEventContent::notice_markdown(message)))
},
}
}
fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String {
let argv = parse_command_line(line);
fn parse_command(line: &str) -> Result<(AdminCommand, Vec<String>)> {
let argv = parse_line(line);
let command = AdminCommand::try_parse_from(&argv)?;
Ok((command, argv))
}
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));
'token: for token in argv.into_iter().skip(1) {
@@ -196,7 +186,7 @@ fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String {
}
// 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
.split_whitespace()
.map(str::to_owned)
@@ -208,8 +198,7 @@ fn parse_command_line(command_line: &str) -> Vec<String> {
}
// 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(server_user) {
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with('@') {
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 tracing::{debug, info};
use crate::services;
use crate::admin_command;
#[admin_command]
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> {
if event_id.is_some() && mxc.is_some() {
return Ok(RoomMessageEventContent::text_plain(
@@ -15,7 +15,7 @@ pub(super) async fn delete(
if let Some(mxc) = 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(
"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;
// 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") {
debug!("Event ID has \"content\".");
let content_obj = content_key.as_object();
@@ -123,7 +123,7 @@ pub(super) async fn delete(
}
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);
}
@@ -138,23 +138,26 @@ pub(super) async fn delete(
))
}
pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"Expected code block in command body. Add --help for details.",
));
}
let mxc_list = body
.clone()
.drain(1..body.len().checked_sub(1).unwrap())
let mxc_list = self
.body
.to_vec()
.drain(1..self.body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>();
let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc).await?;
self.services.media.delete(mxc).await?;
mxc_deletion_count = mxc_deletion_count
.checked_add(1)
.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(
_body: Vec<&str>, duration: String, force: bool,
) -> Result<RoomMessageEventContent> {
let deleted_count = services()
#[admin_command]
pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
let deleted_count = self
.services
.media
.delete_all_remote_media_at_after_time(duration, force)
.await?;
+4 -18
View File
@@ -2,12 +2,12 @@ mod commands;
use clap::Subcommand;
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))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum MediaCommand {
/// - Deletes a single media file from our database and on the filesystem
/// via a single MXC URL
@@ -37,17 +37,3 @@ pub(super) enum MediaCommand {
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::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 check;
pub(crate) mod debug;
pub(crate) mod federation;
pub(crate) mod handler;
pub(crate) mod media;
pub(crate) mod query;
pub(crate) mod room;
pub(crate) mod server;
mod tests;
pub(crate) mod user;
pub(crate) mod utils;
extern crate conduit_api as api;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
pub(crate) use service::{services, user_is_local};
pub(crate) use conduit::Result;
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
pub(crate) use crate::{
handler::Service,
command::Command,
utils::{escape_html, get_room_info},
};
mod_ctor! {}
mod_dtor! {}
pub(crate) const PAGE_SIZE: usize = 100;
conduit::mod_ctor! {}
conduit::mod_dtor! {}
conduit::rustc_flags_capture! {}
/// Install the admin command handler
pub async fn init() {
_ = services()
.admin
pub async fn init(admin_service: &service::admin::Service) {
_ = admin_service
.complete
.write()
.expect("locked for writing")
.insert(handler::complete);
_ = services()
.admin
.handle
.write()
.await
.insert(handler::handle);
_ = admin_service.handle.write().await.insert(handler::handle);
}
/// Uninstall the admin command handler
pub async fn fini() {
_ = services().admin.handle.write().await.take();
_ = services()
.admin
pub async fn fini(admin_service: &service::admin::Service) {
_ = admin_service.handle.write().await.take();
_ = admin_service
.complete
.write()
.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::{services, Result};
use crate::Command;
#[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
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 {
AccountData::ChangesSince {
AccountDataCommand::ChangesSince {
user_id,
since,
room_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
let results = services
.account_data
.changes_since(room_id.as_deref(), &user_id, since)?;
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```"
)))
},
AccountData::Get {
AccountDataCommand::Get {
user_id,
kind,
room_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
let results = services
.account_data
.get(room_id.as_deref(), &user_id, kind)?;
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 super::Appservice;
use crate::{services, Result};
use crate::Command;
#[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
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 {
Appservice::GetRegistration {
AppserviceCommand::GetRegistration {
appservice_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
let results = services
.appservice
.db
.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```"
)))
},
Appservice::All => {
AppserviceCommand::All => {
let timer = tokio::time::Instant::now();
let results = services().appservice.all();
let results = services.appservice.all();
let query_time = timer.elapsed();
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::{services, Result};
use crate::Command;
#[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
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 {
Globals::DatabaseVersion => {
GlobalsCommand::DatabaseVersion => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::CurrentCount => {
GlobalsCommand::CurrentCount => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::LastCheckForUpdatesId => {
GlobalsCommand::LastCheckForUpdatesId => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::LoadKeypair => {
GlobalsCommand::LoadKeypair => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::SigningKeysFor {
GlobalsCommand::SigningKeysFor {
origin,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
+15 -278
View File
@@ -10,314 +10,51 @@ mod users;
use clap::Subcommand;
use conduit::Result;
use room_state_cache::room_state_cache;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId,
};
use self::{
account_data::account_data, appservice::appservice, globals::globals, presence::presence, resolver::resolver,
room_alias::room_alias, sending::sending, users::users,
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
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))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Query tables from database
pub(super) enum QueryCommand {
/// - account_data.rs iterators and getters
#[command(subcommand)]
AccountData(AccountData),
AccountData(AccountDataCommand),
/// - appservice.rs iterators and getters
#[command(subcommand)]
Appservice(Appservice),
Appservice(AppserviceCommand),
/// - presence.rs iterators and getters
#[command(subcommand)]
Presence(Presence),
Presence(PresenceCommand),
/// - rooms/alias.rs iterators and getters
#[command(subcommand)]
RoomAlias(RoomAlias),
RoomAlias(RoomAliasCommand),
/// - rooms/state_cache iterators and getters
#[command(subcommand)]
RoomStateCache(RoomStateCache),
RoomStateCache(RoomStateCacheCommand),
/// - globals.rs iterators and getters
#[command(subcommand)]
Globals(Globals),
Globals(GlobalsCommand),
/// - sending.rs iterators and getters
#[command(subcommand)]
Sending(Sending),
Sending(SendingCommand),
/// - users.rs iterators and getters
#[command(subcommand)]
Users(Users),
Users(UsersCommand),
/// - resolver service
#[command(subcommand)]
Resolver(Resolver),
}
#[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?,
})
Resolver(ResolverCommand),
}
+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::{services, Result};
use crate::Command;
#[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
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 {
Presence::GetPresence {
PresenceCommand::GetPresence {
user_id,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Presence::PresenceSince {
PresenceCommand::PresenceSince {
since,
} => {
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 query_time = timer.elapsed();
+27 -20
View File
@@ -1,25 +1,29 @@
use std::fmt::Write;
use clap::Subcommand;
use conduit::{utils::time, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use super::Resolver;
use crate::services;
use crate::{admin_command, admin_command_dispatch};
/// All the getters and iterators in key_value/users.rs
pub(super) async fn resolver(subcommand: Resolver) -> Result<RoomMessageEventContent> {
match subcommand {
Resolver::DestinationsCache {
server_name,
} => destinations_cache(server_name).await,
Resolver::OverridesCache {
name,
} => overrides_cache(name).await,
}
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Resolver service and caches
pub(crate) enum ResolverCommand {
/// Query the destinations cache
DestinationsCache {
server_name: Option<OwnedServerName>,
},
/// Query the overrides cache
OverridesCache {
name: Option<String>,
},
}
async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
use service::sending::CachedDest;
#[admin_command]
async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedDest;
let mut out = String::new();
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");
};
let map = services()
.globals
let map = self
.services
.resolver
.cache
.destinations
.read()
.expect("locked");
@@ -52,8 +57,9 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room
Ok(RoomMessageEventContent::notice_markdown(out))
}
async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::sending::CachedOverride;
#[admin_command]
async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedOverride;
let mut out = String::new();
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");
};
let map = services()
.globals
let map = self
.services
.resolver
.cache
.overrides
.read()
.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::{services, Result};
use crate::Command;
#[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
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 {
RoomAlias::ResolveLocalAlias {
RoomAliasCommand::ResolveLocalAlias {
alias,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomAlias::LocalAliasesForRoom {
RoomAliasCommand::LocalAliasesForRoom {
room_id,
} => {
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 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```"
)))
},
RoomAlias::AllLocalAliases => {
RoomAliasCommand::AllLocalAliases => {
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 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::{services, Result};
use crate::Command;
#[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 {
RoomStateCache::ServerInRoom {
RoomStateCacheCommand::ServerInRoom {
server,
room_id,
} => {
let timer = tokio::time::Instant::now();
let result = services()
.rooms
.state_cache
.server_in_room(&server, &room_id);
let result = services.rooms.state_cache.server_in_room(&server, &room_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
},
RoomStateCache::RoomServers {
RoomStateCacheCommand::RoomServers {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
.rooms
.state_cache
.room_servers(&room_id)
.collect();
let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::ServerRooms {
RoomStateCacheCommand::ServerRooms {
server,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomMembers {
RoomStateCacheCommand::RoomMembers {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
.rooms
.state_cache
.room_members(&room_id)
.collect();
let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::LocalUsersInRoom {
RoomStateCacheCommand::LocalUsersInRoom {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services()
let results: Vec<_> = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::ActiveLocalUsersInRoom {
RoomStateCacheCommand::ActiveLocalUsersInRoom {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services()
let results: Vec<_> = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::RoomJoinedCount {
RoomStateCacheCommand::RoomJoinedCount {
room_id,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomInvitedCount {
RoomStateCacheCommand::RoomInvitedCount {
room_id,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomUserOnceJoined {
RoomStateCacheCommand::RoomUserOnceJoined {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
let results: Result<Vec<_>> = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::RoomMembersInvited {
RoomStateCacheCommand::RoomMembersInvited {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
let results: Result<Vec<_>> = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::GetInviteCount {
RoomStateCacheCommand::GetInviteCount {
room_id,
user_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
let results = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::GetLeftCount {
RoomStateCacheCommand::GetLeftCount {
room_id,
user_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
let results = services
.rooms
.state_cache
.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```"
)))
},
RoomStateCache::RoomsJoined {
RoomStateCacheCommand::RoomsJoined {
user_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
.rooms
.state_cache
.rooms_joined(&user_id)
.collect();
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomsInvited {
RoomStateCacheCommand::RoomsInvited {
user_id,
} => {
let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services()
.rooms
.state_cache
.rooms_invited(&user_id)
.collect();
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomsLeft {
RoomStateCacheCommand::RoomsLeft {
user_id,
} => {
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();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::InviteState {
RoomStateCacheCommand::InviteState {
user_id,
room_id,
} => {
let timer = tokio::time::Instant::now();
let results = services()
.rooms
.state_cache
.invite_state(&user_id, &room_id);
let results = services.rooms.state_cache.invite_state(&user_id, &room_id);
let query_time = timer.elapsed();
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::{service::sending::Destination, services, Result};
use crate::Command;
#[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
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 {
Sending::ActiveRequests => {
SendingCommand::ActiveRequests => {
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 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```"
)))
},
Sending::QueuedRequests {
SendingCommand::QueuedRequests {
appservice_id,
server_name,
user_id,
@@ -38,12 +97,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
services()
services
.sending
.db
.queued_requests(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services()
(None, Some(server_name), None, None) => services
.sending
.db
.queued_requests(&Destination::Normal(server_name.into())),
@@ -55,7 +114,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
services()
services
.sending
.db
.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```"
)))
},
Sending::ActiveRequestsFor {
SendingCommand::ActiveRequestsFor {
appservice_id,
server_name,
user_id,
@@ -104,12 +163,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
services()
services
.sending
.db
.active_requests_for(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services()
(None, Some(server_name), None, None) => services
.sending
.db
.active_requests_for(&Destination::Normal(server_name.into())),
@@ -121,7 +180,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
services()
services
.sending
.db
.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```"
)))
},
Sending::GetLatestEduCount {
SendingCommand::GetLatestEduCount {
server_name,
} => {
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();
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 super::Users;
use crate::{services, Result};
use crate::Command;
#[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
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 {
Users::Iter => {
UsersCommand::Iter => {
let timer = tokio::time::Instant::now();
let results = services().users.db.iter();
let results = services.users.db.iter();
let users = results.collect::<Vec<_>>();
let query_time = timer.elapsed();
@@ -1,12 +1,49 @@
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, services, Result};
use crate::{escape_html, Command};
pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
let server_user = &services().globals.server_user;
#[derive(Debug, Subcommand)]
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 {
RoomAliasCommand::Set {
@@ -19,7 +56,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
| RoomAliasCommand::Which {
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) {
Ok(alias) => alias,
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,
room_id,
..
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) {
(true, Ok(Some(id))) => match services()
} => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) {
(true, Ok(Some(id))) => match services
.rooms
.alias
.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!(
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
))),
(_, Ok(None)) => match services()
(_, Ok(None)) => match services
.rooms
.alias
.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 {
..
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => match services()
} => match services.rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => match services
.rooms
.alias
.remove_alias(&room_alias, server_user)
@@ -70,7 +107,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
},
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(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
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,
} => {
if let Some(room_id) = room_id {
let aliases = services()
let aliases = services
.rooms
.alias
.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}"))),
}
} else {
let aliases = services()
let aliases = services
.rooms
.alias
.all_local_aliases()
.collect::<Result<Vec<_>, _>>();
match aliases {
Ok(aliases) => {
let server_name = services().globals.server_name();
let server_name = services.globals.server_name();
let plain_list = aliases
.iter()
.fold(String::new(), |mut output, (alias, id)| {
@@ -1,15 +1,18 @@
use std::fmt::Write;
use conduit::Result;
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(
_body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
#[admin_command]
pub(super) async fn list_rooms(
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
) -> Result<RoomMessageEventContent> {
// 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 mut rooms = services()
let mut rooms = self
.services
.rooms
.metadata
.iter_ids()
@@ -18,7 +21,8 @@ pub(super) async fn list(
.ok()
.filter(|room_id| {
if exclude_disabled
&& services()
&& self
.services
.rooms
.metadata
.is_disabled(room_id)
@@ -28,7 +32,8 @@ pub(super) async fn list(
}
if exclude_banned
&& services()
&& self
.services
.rooms
.metadata
.is_banned(room_id)
@@ -39,7 +44,7 @@ pub(super) async fn list(
true
})
.map(|room_id| get_room_info(&room_id))
.map(|room_id| get_room_info(self.services, &room_id))
})
.collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1);
@@ -1,21 +1,43 @@
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, handler::PAGE_SIZE, services, Result};
use crate::{escape_html, get_room_info, Command, PAGE_SIZE};
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 {
RoomDirectoryCommand::Publish {
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")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
},
RoomDirectoryCommand::Unpublish {
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")),
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
let page = page.unwrap_or(1);
let mut rooms = services()
let mut rooms = services
.rooms
.directory
.public_rooms()
.filter_map(Result::ok)
.map(|id: OwnedRoomId| get_room_info(&id))
.map(|id: OwnedRoomId| get_room_info(services, &id))
.collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1);
rooms.reverse();
@@ -1,22 +1,30 @@
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use service::services;
use super::RoomInfoCommand;
use crate::Result;
use crate::{admin_command, admin_command_dispatch};
pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
RoomInfoCommand::ListJoinedMembers {
room_id,
} => list_joined_members(body, room_id).await,
RoomInfoCommand::ViewRoomTopic {
room_id,
} => view_room_topic(body, room_id).await,
}
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(crate) 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>,
},
}
async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let room_name = services()
#[admin_command]
async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let room_name = self
.services
.rooms
.state_accessor
.get_name(&room_id)
@@ -24,7 +32,8 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.flatten()
.unwrap_or_else(|| room_id.to_string());
let members = services()
let members = self
.services
.rooms
.state_cache
.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| {
(
user_id.clone(),
services()
self.services
.users
.displayname(&user_id)
.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))
}
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else {
#[admin_command]
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."));
};
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 room_commands;
mod room_directory_commands;
mod room_info_commands;
mod room_moderation_commands;
mod alias;
mod commands;
mod directory;
mod info;
mod moderation;
use clap::Subcommand;
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))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum RoomCommand {
/// - List all rooms the server knows about
List {
#[clap(alias = "list")]
ListRooms {
page: Option<usize>,
/// Excludes rooms that we have federation disabled with
@@ -42,153 +45,3 @@ pub(super) enum RoomCommand {
/// - Manage the room directory
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 clap::Subcommand;
use conduit::{debug, error, info, warn, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use tracing::{debug, error, info, warn};
use super::{super::Service, RoomModerationCommand};
use crate::{get_room_info, services, user_is_local, Result};
use crate::{admin_command, admin_command_dispatch, get_room_info};
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
RoomModerationCommand::BanRoom {
force,
room,
disable_federation,
} => ban_room(body, force, room, disable_federation).await,
RoomModerationCommand::BanListOfRooms {
force,
disable_federation,
} => ban_list_of_rooms(body, force, disable_federation).await,
RoomModerationCommand::UnbanRoom {
room,
enable_federation,
} => unban_room(body, room, enable_federation).await,
RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await,
}
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(crate) 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,
}
#[admin_command]
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> {
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) {
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");
services().rooms.metadata.ban_room(&room_id, true)?;
self.services.rooms.metadata.ban_room(&room_id, true)?;
room_id
} else if room.is_room_alias_id() {
@@ -69,12 +109,13 @@ async fn ban_room(
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
} else {
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
.alias
.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
} else {
@@ -104,20 +145,21 @@ async fn ban_room(
debug!("Making all users leave the room {}", &room);
if force {
for local_user in services()
for local_user in self
.services
.rooms
.state_cache
.room_members(&room_id)
.filter_map(|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
// who are in the admin room to the list of local users (would
// 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
// if somehow this fails
&& services()
&& self.services
.users
.is_admin(local_user)
.unwrap_or(true))
@@ -128,30 +170,31 @@ async fn ban_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 {
warn!(%e, "Failed to leave room");
}
}
} else {
for local_user in services()
for local_user in self
.services
.rooms
.state_cache
.room_members(&room_id)
.filter_map(|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
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
== self.services.globals.server_name()
&& !self.services
.users
.is_admin(local_user)
.unwrap_or(false))
})
}) {
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 attempting to make local user {} leave room {} during room banning: {}",
&local_user, &room_id, e
@@ -166,7 +209,7 @@ async fn ban_room(
}
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(
"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> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
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(
"Expected code block in command body. Add --help for details.",
));
}
let rooms_s = body
.clone()
.drain(1..body.len().saturating_sub(1))
let rooms_s = self
.body
.to_vec()
.drain(1..self.body.len().saturating_sub(1))
.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_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 {
match <&RoomOrAliasId>::try_from(room) {
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) {
info!("User specified admin room in bulk ban list, ignoring");
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) {
Ok(room_alias) => {
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
} else {
debug!(
@@ -239,7 +285,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
ID over federation"
);
match services()
match self
.services
.rooms
.alias
.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 {
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");
room_ban_count = room_ban_count.saturating_add(1);
}
debug!("Making all users leave the room {}", &room_id);
if force {
for local_user in services()
for local_user in self
.services
.rooms
.state_cache
.room_members(&room_id)
.filter_map(|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 who are in the admin room to the list of local
// users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
== self.services.globals.server_name()
// since this is a force operation, assume user is an
// admin if somehow this fails
&& services()
&& self.services
.users
.is_admin(local_user)
.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)",
&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");
}
}
} else {
for local_user in services()
for local_user in self
.services
.rooms
.state_cache
.room_members(&room_id)
.filter_map(|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 who are in the admin room to the list of local
// users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
== self.services.globals.server_name()
&& !self.services
.users
.is_admin(local_user)
.unwrap_or(false))
})
}) {
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 attempting to make local user {} leave room {} during bulk room banning: {}",
&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 {
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(
_body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool,
) -> Result<RoomMessageEventContent> {
#[admin_command]
async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
Ok(room_id) => room_id,
@@ -406,7 +460,7 @@ async fn unban_room(
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
} else if room.is_room_alias_id() {
@@ -425,12 +479,13 @@ async fn unban_room(
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
} else {
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
.alias
.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
} else {
@@ -459,7 +514,7 @@ async fn unban_room(
};
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."));
}
@@ -469,8 +524,10 @@ async fn unban_room(
))
}
async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let rooms = services()
#[admin_command]
async fn list_banned_rooms(&self) -> Result<RoomMessageEventContent> {
let rooms = self
.services
.rooms
.metadata
.list_banned_rooms()
@@ -484,13 +541,13 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
let mut rooms = room_ids
.into_iter()
.map(|room_id| get_room_info(&room_id))
.map(|room_id| get_room_info(self.services, &room_id))
.collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1);
rooms.reverse();
let output_plain = format!(
"Rooms Banned ({}):\n```\n{}```",
"Rooms Banned ({}):\n```\n{}\n```",
rooms.len(),
rooms
.iter()
+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 crate::services;
use crate::admin_command;
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let elapsed = services()
#[admin_command]
pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
let elapsed = self
.services
.server
.started
.elapsed()
@@ -14,14 +18,58 @@ pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent>
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
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> {
let services_usage = services().memory_usage().await?;
let database_usage = services().db.db.memory_usage()?;
#[admin_command]
pub(super) async fn list_features(
&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}"));
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> {
services().clear_cache().await;
#[admin_command]
pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
self.services.clear_cache().await;
Ok(RoomMessageEventContent::text_plain("Done."))
}
pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.backup_list()?;
#[admin_command]
pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.backup_list()?;
if result.is_empty() {
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> {
let mut result = services()
#[admin_command]
pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
let globals = Arc::clone(&self.services.globals);
let mut result = self
.services
.server
.runtime()
.spawn_blocking(move || match services().globals.db.backup() {
.spawn_blocking(move || match globals.db.backup() {
Ok(()) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
.await?;
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))
}
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(" ");
services().admin.send_text(&message).await;
self.services.admin.send_text(&message).await;
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
}
#[cfg(conduit_mods)]
pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
services().server.reload()?;
#[admin_command]
pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
self.services.server.reload()?;
Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
}
#[admin_command]
#[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;
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..."))
}
pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> {
warn!("shutdown command");
services().server.shutdown()?;
self.services.server.shutdown()?;
Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
}
+17 -28
View File
@@ -2,12 +2,11 @@ mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*;
use crate::admin_command_dispatch;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum ServerCommand {
/// - Time elapsed since startup
Uptime,
@@ -15,6 +14,18 @@ pub(super) enum ServerCommand {
/// - Show configuration values
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
MemoryUsage,
@@ -36,9 +47,9 @@ pub(super) enum ServerCommand {
message: Vec<String>,
},
#[cfg(conduit_mods)]
/// - Hot-reload the server
Reload,
#[clap(alias = "reload")]
ReloadMods,
#[cfg(unix)]
/// - Restart the server
@@ -50,25 +61,3 @@ pub(super) enum ServerCommand {
/// - Shutdown the server
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) {
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 {
panic!("no error!");
+113 -81
View File
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, fmt::Write as _};
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::{
events::{
room::message::RoomMessageEventContent,
@@ -10,17 +10,17 @@ use ruma::{
},
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
};
use tracing::{error, info, warn};
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},
};
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
match services().users.list_local_users() {
#[admin_command]
pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
match self.services.users.list_local_users() {
Ok(users) => {
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
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(
_body: Vec<&str>, username: String, password: Option<String>,
) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
// 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")));
}
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));
// 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
let mut displayname = user_id.localpart().to_owned();
// 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
if !services()
if !self
.services
.globals
.config
.new_user_displayname_suffix
.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");
}
services()
self.services
.users
.set_displayname(&user_id, Some(displayname))
.await?;
// Initial account data
services().account_data.update(
self.services.account_data.update(
None,
&user_id,
ruma::events::GlobalAccountDataEventType::PushRules
@@ -82,12 +90,13 @@ pub(super) async fn create(
.expect("to json value always works"),
)?;
if !services().globals.config.auto_join_rooms.is_empty() {
for room in &services().globals.config.auto_join_rooms {
if !services()
if !self.services.globals.config.auto_join_rooms.is_empty() {
for room in &self.services.globals.config.auto_join_rooms {
if !self
.services
.rooms
.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.");
continue;
@@ -95,10 +104,11 @@ pub(super) async fn create(
if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper(
self.services,
&user_id,
room,
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,
)
.await
@@ -123,38 +133,38 @@ pub(super) async fn create(
)))
}
pub(super) async fn deactivate(
_body: Vec<&str>, no_leave_rooms: bool, user_id: String,
) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
// 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
if user_id == services().globals.server_user {
if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain(
"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 {
services()
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Making {user_id} leave all rooms after deactivation..."
)))
.await;
let all_joined_rooms: Vec<OwnedRoomId> = services()
let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.collect();
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(&user_id).await;
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(self.services, &user_id).await;
}
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> {
let user_id = parse_local_user_id(&username)?;
#[admin_command]
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(
"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);
match services()
match self
.services
.users
.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(
body: Vec<&str>, no_leave_rooms: bool, force: bool,
) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
#[admin_command]
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: 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(
"Expected code block in command body. Add --help for details.",
));
}
let usernames = body
.clone()
.drain(1..body.len().saturating_sub(1))
let usernames = self
.body
.to_vec()
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>();
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
let mut admins = Vec::new();
for username in usernames {
match parse_active_local_user_id(username) {
match parse_active_local_user_id(self.services, username) {
Ok(user_id) => {
if services().users.is_admin(&user_id)? && !force {
services()
if self.services.users.is_admin(&user_id)? && !force {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"{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
if user_id == services().globals.server_user {
services()
if user_id == self.services.globals.server_user {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"{username} is the server service account, skipping over"
@@ -231,7 +244,7 @@ pub(super) async fn deactivate_all(
user_ids.push(user_id);
},
Err(e) => {
services()
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"{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;
for user_id in user_ids {
match services().users.deactivate_account(&user_id) {
match self.services.users.deactivate_account(&user_id) {
Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1);
if !no_leave_rooms {
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
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.collect();
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(&user_id).await;
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(self.services, &user_id).await;
}
},
Err(e) => {
services()
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
.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
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
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.map(|room_id| get_room_info(&room_id))
.map(|room_id| get_room_info(self.services, &room_id))
.collect();
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))
}
#[admin_command]
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> {
let user_id = parse_local_user_id(&user_id)?;
let room_id = services().rooms.alias.resolve(&room_id).await?;
let user_id = parse_local_user_id(self.services, &user_id)?;
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");
join_room_by_id_helper(&user_id, &room_id, None, &[], None).await?;
assert!(
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!(
"{user_id} has been joined to {room_id}.",
)))
}
pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?;
let displayname = services()
#[admin_command]
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?;
let displayname = self
.services
.users
.displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string());
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
service::admin::make_user_admin(&user_id, displayname).await?;
assert!(
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!(
"{user_id} has been granted admin privileges.",
)))
}
#[admin_command]
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> {
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
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
@@ -386,7 +416,7 @@ pub(super) async fn put_room_tag(
.tags
.insert(tag.clone().into(), TagInfo::new());
services().account_data.update(
self.services.account_data.update(
Some(&room_id),
&user_id,
RoomAccountDataEventType::Tag,
@@ -398,12 +428,14 @@ pub(super) async fn put_room_tag(
)))
}
#[admin_command]
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> {
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
.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());
services().account_data.update(
self.services.account_data.update(
Some(&room_id),
&user_id,
RoomAccountDataEventType::Tag,
@@ -430,12 +462,12 @@ pub(super) async fn delete_room_tag(
)))
}
pub(super) async fn get_room_tags(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?;
#[admin_command]
pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(self.services, &user_id)?;
let event = services()
let event = self
.services
.account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
+8 -51
View File
@@ -2,15 +2,16 @@ mod commands;
use clap::Subcommand;
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))]
#[derive(Subcommand)]
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum UserCommand {
/// - Create a new user
Create {
#[clap(alias = "create")]
CreateUser {
/// Username of the new user
username: String,
/// 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,
#[clap(alias = "list")]
ListUsers,
/// - Lists all the rooms (local and remote) that the specified user is
/// joined in
@@ -102,48 +104,3 @@ pub(super) enum UserCommand {
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 service::user_is_local;
use crate::{services, Result};
use service::Services;
pub(crate) fn escape_html(s: &str) -> String {
s.replace('&', "&amp;")
@@ -10,17 +8,17 @@ pub(crate) fn escape_html(s: &str) -> String {
.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(),
services()
services
.rooms
.state_cache
.room_joined_count(id)
.ok()
.flatten()
.unwrap_or(0),
services()
services
.rooms
.state_accessor
.get_name(id)
@@ -31,16 +29,16 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
}
/// Parses user ID
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
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())
.map_err(|e| err!("The supplied username is not a valid username: {e}"))
}
/// Parses user ID as our local user
pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_user_id(user_id)?;
pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
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.");
}
@@ -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
pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_local_user_id(user_id)?;
pub(crate) fn parse_active_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
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.");
}
if services().users.is_deactivated(&user_id)? {
if services.users.is_deactivated(&user_id)? {
return Err!("User {user_id:?} is deactivated.");
}
-2
View File
@@ -47,7 +47,6 @@ hmac.workspace = true
http.workspace = true
http-body-util.workspace = true
hyper.workspace = true
image.workspace = true
ipaddress.workspace = true
jsonwebtoken.workspace = true
log.workspace = true
@@ -60,7 +59,6 @@ serde.workspace = true
sha-1.workspace = true
tokio.workspace = true
tracing.workspace = true
webpage.workspace = true
[lints]
workspace = true
+80 -77
View File
@@ -1,7 +1,8 @@
use std::fmt::Write;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::debug_info;
use conduit::{debug_info, error, info, utils, warn, Error, Result};
use register::RegistrationKind;
use ruma::{
api::client::{
@@ -17,15 +18,9 @@ use ruma::{
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
push, OwnedRoomId, UserId,
};
use tracing::{error, info, warn};
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{
service::user_is_local,
services,
utils::{self},
Error, Result, Ruma,
};
use crate::Ruma;
const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -42,20 +37,21 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
/// invalid when trying to register
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
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> {
// 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()
.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."))?;
// 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."));
}
if services()
if services
.globals
.forbidden_usernames()
.is_match(user_id.localpart())
@@ -91,9 +87,9 @@ pub(crate) async fn get_register_available_route(
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register")]
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> {
if !services().globals.allow_registration() && body.appservice_info.is_none() {
if !services.globals.allow_registration() && body.appservice_info.is_none() {
info!(
"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;
if is_guest
&& (!services().globals.allow_guest_registration()
|| (services().globals.allow_registration() && services().globals.config.registration_token.is_some()))
&& (!services.globals.allow_guest_registration()
|| (services.globals.allow_registration() && services.globals.config.registration_token.is_some()))
{
info!(
"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
// generic user error.
if is_guest && services().users.count()? < 2 {
if is_guest && services.users.count()? < 2 {
warn!(
"Guest account attempted to register before a real admin user has been registered, rejecting \
registration. Guest's initial device name: {:?}",
@@ -133,16 +129,16 @@ pub(crate) async fn register_route(
let user_id = match (&body.username, is_guest) {
(Some(username), false) => {
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()
.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."))?;
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."));
}
if services()
if services
.globals
.forbidden_usernames()
.is_match(proposed_user_id.localpart())
@@ -155,10 +151,10 @@ pub(crate) async fn register_route(
_ => loop {
let proposed_user_id = UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
services().globals.server_name(),
services.globals.server_name(),
)
.unwrap();
if !services().users.exists(&proposed_user_id)? {
if !services.users.exists(&proposed_user_id)? {
break proposed_user_id;
}
},
@@ -172,13 +168,13 @@ pub(crate) async fn register_route(
} else {
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."));
}
// UIAA
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
uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
@@ -206,8 +202,8 @@ pub(crate) async fn register_route(
if !skip_auth {
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services().uiaa.try_auth(
&UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid"),
let (worked, uiaainfo) = services.uiaa.try_auth(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
@@ -218,8 +214,8 @@ pub(crate) async fn register_route(
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services().uiaa.create(
&UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid"),
services.uiaa.create(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(),
&uiaainfo,
&json,
@@ -237,25 +233,25 @@ pub(crate) async fn register_route(
};
// Create user
services().users.create(&user_id, password)?;
services.users.create(&user_id, password)?;
// Default to pretty displayname
let mut displayname = user_id.localpart().to_owned();
// 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
if !services().globals.new_user_displayname_suffix().is_empty() {
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
if !services.globals.new_user_displayname_suffix().is_empty() && body.appservice_info.is_none() {
write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
services()
services
.users
.set_displayname(&user_id, Some(displayname.clone()))
.await?;
// Initial account data
services().account_data.update(
services.account_data.update(
None,
&user_id,
GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -290,16 +286,20 @@ pub(crate) async fn register_route(
let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account
services()
.users
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
services.users.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)?;
debug_info!(%user_id, %device_id, "User account was created");
// log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest {
info!("New user \"{user_id}\" registered on this server.");
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"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
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.");
if let Some(device_display_name) = &body.initial_device_display_name {
@@ -317,7 +317,7 @@ pub(crate) async fn register_route(
.as_ref()
.is_some_and(|device_display_name| !device_display_name.is_empty())
{
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"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;
} else {
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"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;
}
} else {
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"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
// users Note: the server user, @conduit:servername, is generated first
if !is_guest {
if let Some(admin_room) = service::admin::Service::get_admin_room()? {
if services()
.rooms
.state_cache
.room_joined_count(&admin_room)?
== Some(1)
{
service::admin::make_user_admin(&user_id, displayname).await?;
if let Some(admin_room) = services.admin.get_admin_room()? {
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
services
.admin
.make_user_admin(&user_id, displayname)
.await?;
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()
&& !services().globals.config.auto_join_rooms.is_empty()
&& (services().globals.allow_guests_auto_join_rooms() || !is_guest)
&& !services.globals.config.auto_join_rooms.is_empty()
&& (services.globals.allow_guests_auto_join_rooms() || !is_guest)
{
for room in &services().globals.config.auto_join_rooms {
if !services()
for room in &services.globals.config.auto_join_rooms {
if !services
.rooms
.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.");
continue;
@@ -376,10 +374,11 @@ pub(crate) async fn register_route(
if let Some(room_id_server_name) = room.server_name() {
if let Err(e) = join_room_by_id_helper(
&services,
&user_id,
room,
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,
)
.await
@@ -421,7 +420,8 @@ pub(crate) async fn register_route(
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
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> {
// Authentication for this endpoint was made optional, but we need
// authentication currently
@@ -442,7 +442,7 @@ pub(crate) async fn change_password_route(
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services()
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked {
@@ -451,7 +451,7 @@ pub(crate) async fn change_password_route(
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
@@ -459,24 +459,24 @@ pub(crate) async fn change_password_route(
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
services()
services
.users
.set_password(sender_user, Some(&body.new_password))?;
if body.logout_devices {
// Logout all devices except the current one
for id in services()
for id in services
.users
.all_device_ids(sender_user)
.filter_map(Result::ok)
.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.");
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} changed their password."
@@ -491,14 +491,16 @@ pub(crate) async fn change_password_route(
/// Get `user_id` of the sender user.
///
/// 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 device_id = body.sender_device.clone();
Ok(whoami::v3::Response {
user_id: sender_user.clone(),
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
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
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> {
// Authentication for this endpoint was made optional, but we need
// authentication currently
@@ -536,7 +539,7 @@ pub(crate) async fn deactivate_route(
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services()
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked {
@@ -545,7 +548,7 @@ pub(crate) async fn deactivate_route(
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
@@ -554,23 +557,23 @@ pub(crate) async fn deactivate_route(
}
// 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
let all_joined_rooms: Vec<OwnedRoomId> = services()
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
.collect();
super::update_displayname(sender_user.clone(), None, all_joined_rooms.clone()).await?;
super::update_avatar_url(sender_user.clone(), None, None, all_joined_rooms).await?;
super::update_displayname(&services, sender_user.clone(), None, all_joined_rooms.clone()).await?;
super::update_avatar_url(&services, sender_user.clone(), None, None, all_joined_rooms).await?;
// 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.");
services()
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"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
/// there is only one registration token allowed.
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> {
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(
ErrorKind::forbidden(),
"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 ruma::{
api::client::{
@@ -6,21 +8,27 @@ use ruma::{
},
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}`
///
/// 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");
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
// allow removing forbidden room aliases
if services()
if services
.globals
.forbidden_alias_names()
.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."));
}
if services()
if services
.rooms
.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."));
}
services()
services
.rooms
.alias
.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.
///
/// - 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");
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
.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."));
}
services()
services
.rooms
.alias
.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}`
///
/// 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 servers = None;
let Ok((room_id, pre_servers)) = services()
let Ok((room_id, pre_servers)) = services
.rooms
.alias
.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."));
};
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:?}");
Ok(get_alias::v3::Response::new(room_id, 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> {
// find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services()
let mut servers: Vec<OwnedServerName> = services
.rooms
.state_cache
.room_servers(room_id)
@@ -124,10 +140,10 @@ fn room_available_servers(
// prefer the room alias server first
if let Some(server_index) = servers
.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.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
.iter()
.position(|server| server == room_alias.server_name())
+49 -62
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{
api::client::{
backup::{
@@ -11,16 +12,16 @@ use ruma::{
UInt,
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `POST /_matrix/client/r0/room_keys/version`
///
/// Creates a new backup.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let version = services()
let version = services
.key_backups
.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
/// modified.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
services
.key_backups
.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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let (version, algorithm) = services()
let (version, algorithm) = services
.key_backups
.get_latest_backup(sender_user)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
Ok(get_latest_backup_info::v3::Response {
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")),
etag: services().key_backups.get_etag(sender_user, &version)?,
etag: services.key_backups.get_etag(sender_user, &version)?,
version,
})
}
@@ -70,10 +71,10 @@ pub(crate) async fn get_latest_backup_info_route(
///
/// Get information about an existing backup.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let algorithm = services()
let algorithm = services
.key_backups
.get_backup(sender_user, &body.version)?
.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 {
algorithm,
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.key_backups.get_etag(sender_user, &body.version)?,
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
/// to the backup
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
services
.key_backups
.delete_backup(sender_user, &body.version)?;
@@ -120,12 +119,12 @@ pub(crate) async fn delete_backup_version_route(
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services()
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
@@ -138,7 +137,7 @@ pub(crate) async fn add_backup_keys_route(
for (room_id, room) in &body.rooms {
for (session_id, key_data) in &room.sessions {
services()
services
.key_backups
.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 {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.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
/// - Returns the new number of keys in this backup and the etag
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services()
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
@@ -183,21 +180,19 @@ pub(crate) async fn add_backup_keys_for_room_route(
}
for (session_id, key_data) in &body.sessions {
services()
services
.key_backups
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
}
Ok(add_backup_keys_for_room::v3::Response {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.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
/// - Returns the new number of keys in this backup and the etag
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services()
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
@@ -226,20 +221,18 @@ pub(crate) async fn add_backup_keys_for_session_route(
));
}
services()
services
.key_backups
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
Ok(add_backup_keys_for_session::v3::Response {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.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.
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> {
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 {
rooms,
@@ -262,11 +255,11 @@ pub(crate) async fn get_backup_keys_route(
///
/// Retrieves all keys from the backup for a given room.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sessions = services()
let sessions = services
.key_backups
.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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let key_data = services()
let key_data = services
.key_backups
.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."))?;
@@ -297,24 +290,22 @@ pub(crate) async fn get_backup_keys_for_session_route(
///
/// Delete the keys from the backup.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
services
.key_backups
.delete_all_keys(sender_user, &body.version)?;
Ok(delete_backup_keys::v3::Response {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
services
.key_backups
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
Ok(delete_backup_keys_for_room::v3::Response {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
services
.key_backups
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
Ok(delete_backup_keys_for_session::v3::Response {
count: (UInt::try_from(
services()
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
etag: services.key_backups.get_etag(sender_user, &body.version)?,
})
}
+6 -5
View File
@@ -1,29 +1,30 @@
use std::collections::BTreeMap;
use axum::extract::State;
use ruma::api::client::discovery::get_capabilities::{
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
};
use crate::{services, Result, Ruma};
use crate::{Result, Ruma};
/// # `GET /_matrix/client/v3/capabilities`
///
/// Get information on the supported feature set and other relevent capabilities
/// of this server.
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> {
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);
}
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);
}
let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability {
default: services().globals.default_room_version(),
default: services.globals.default_room_version(),
available,
};
+19 -10
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{
api::client::{
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_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}`
///
/// Sets some account data for the sender user.
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> {
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 {})
}
@@ -27,9 +34,10 @@ pub(crate) async fn set_global_account_data_route(
///
/// Sets some room account data for the sender user.
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> {
set_account_data(
&services,
Some(&body.room_id),
&body.sender_user,
&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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services()
let event: Box<RawJsonValue> = services
.account_data
.get(None, sender_user, body.event_type.to_string().into())?
.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.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services()
let event: Box<RawJsonValue> = services
.account_data
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
.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(
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<()> {
let sender_user = sender_user.as_ref().expect("user is authenticated");
let data: serde_json::Value =
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,
sender_user,
event_type.into(),
+24 -19
View File
@@ -1,12 +1,13 @@
use std::collections::HashSet;
use axum::extract::State;
use ruma::{
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
events::StateEventType,
};
use tracing::error;
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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
/// 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_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 {
LazyLoadOptions::Enabled {
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 base_token = services()
let base_token = services
.rooms
.timeline
.get_pdu_count(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?;
let base_event = services()
let base_event = services
.rooms
.timeline
.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();
if !services()
if !services
.rooms
.state_accessor
.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_device,
&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 events_before: Vec<_> = services()
let events_before: Vec<_> = services
.rooms
.timeline
.pdus_until(sender_user, &room_id, base_token)?
.take(limit / 2)
.filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| {
services()
services
.rooms
.state_accessor
.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();
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_device,
&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())
.collect();
let events_after: Vec<_> = services()
let events_after: Vec<_> = services
.rooms
.timeline
.pdus_after(sender_user, &room_id, base_token)?
.take(limit / 2)
.filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| {
services()
services
.rooms
.state_accessor
.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();
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_device,
&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
.state_accessor
.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(
services()
services
.rooms
.state
.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,
);
let state_ids = services()
let state_ids = services
.rooms
.state_accessor
.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());
for (shortstatekey, id) in state_ids {
let (event_type, state_key) = services()
let (event_type, state_key) = services
.rooms
.short
.get_statekey_from_short(shortstatekey)?;
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);
continue;
};
state.push(pdu.to_state_event());
} 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);
continue;
};
+25 -18
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::api::client::{
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
error::ErrorKind,
@@ -5,15 +6,17 @@ use ruma::api::client::{
};
use super::SESSION_ID_LENGTH;
use crate::{services, utils, Error, Result, Ruma};
use crate::{utils, Error, Result, Ruma};
/// # `GET /_matrix/client/r0/devices`
///
/// 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 devices: Vec<device::Device> = services()
let devices: Vec<device::Device> = services
.users
.all_devices_metadata(sender_user)
.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 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 device = services()
let device = services
.users
.get_device_metadata(sender_user, &body.body.device_id)?
.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}`
///
/// 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 mut device = services()
let mut device = services
.users
.get_device_metadata(sender_user, &body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
device.display_name.clone_from(&body.display_name);
services()
services
.users
.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)
/// - Forgets to-device events
/// - 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_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 {
let (worked, uiaainfo) = services()
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked {
@@ -95,7 +104,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
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."));
}
services()
.users
.remove_device(sender_user, &body.device_id)?;
services.users.remove_device(sender_user, &body.device_id)?;
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
/// - Triggers device list updates
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> {
let sender_user = body.sender_user.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 {
let (worked, uiaainfo) = services()
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked {
@@ -149,7 +156,7 @@ pub(crate) async fn delete_devices_route(
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
@@ -158,7 +165,7 @@ pub(crate) async fn delete_devices_route(
}
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 {})
+41 -34
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Error, Result};
use ruma::{
@@ -19,8 +20,9 @@ use ruma::{
},
uint, RoomId, ServerName, UInt, UserId,
};
use service::Services;
use crate::{service::server_is_ours, services, Ruma};
use crate::Ruma;
/// # `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
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
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> {
if let Some(server) = &body.server {
if services()
if services
.globals
.forbidden_remote_room_directory_server_names()
.contains(server)
@@ -45,6 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
}
let response = get_public_rooms_filtered_helper(
&services,
body.server.as_deref(),
body.limit,
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
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
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> {
if let Some(server) = &body.server {
if services()
if services
.globals
.forbidden_remote_room_directory_server_names()
.contains(server)
@@ -83,6 +88,7 @@ pub(crate) async fn get_public_rooms_route(
}
let response = get_public_rooms_filtered_helper(
&services,
body.server.as_deref(),
body.limit,
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.
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
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> {
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 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(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
@@ -126,7 +133,7 @@ pub(crate) async fn set_room_visibility_route(
match &body.visibility {
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!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"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);
},
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(
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.
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> {
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 Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
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
} else {
room::Visibility::Private
@@ -175,10 +182,11 @@ pub(crate) async fn get_room_visibility_route(
}
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> {
if let Some(other_server) = server.filter(|server_name| !server_is_ours(server_name)) {
let response = services()
if let Some(other_server) = server.filter(|server_name| !services.globals.server_is_ours(server_name)) {
let response = services
.sending
.send_federation_request(
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
.directory
.public_rooms()
@@ -232,12 +240,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
let room_id = room_id?;
let chunk = PublicRoomsChunk {
canonical_alias: services()
canonical_alias: services
.rooms
.state_accessor
.get_canonical_alias(&room_id)?,
name: services().rooms.state_accessor.get_name(&room_id)?,
num_joined_members: services()
name: services.rooms.state_accessor.get_name(&room_id)?,
num_joined_members: services
.rooms
.state_cache
.room_joined_count(&room_id)?
@@ -247,24 +255,24 @@ pub(crate) async fn get_public_rooms_filtered_helper(
})
.try_into()
.expect("user count should not be that big"),
topic: services()
topic: services
.rooms
.state_accessor
.get_room_topic(&room_id)
.unwrap_or(None),
world_readable: services().rooms.state_accessor.is_world_readable(&room_id)?,
guest_can_join: services()
world_readable: services.rooms.state_accessor.is_world_readable(&room_id)?,
guest_can_join: services
.rooms
.state_accessor
.guest_can_join(&room_id)?,
avatar_url: services()
avatar_url: services
.rooms
.state_accessor
.get_avatar(&room_id)?
.into_option()
.unwrap_or_default()
.url,
join_rule: services()
join_rule: services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
@@ -282,7 +290,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
.transpose()?
.flatten()
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
room_type: services()
room_type: services
.rooms
.state_accessor
.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
/// room history visibility event or room creator
fn user_can_publish_room(user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) =
services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
{
serde_json::from_str(event.content.get())
.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)
})
} else if let Some(event) =
services()
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
+10 -5
View File
@@ -1,18 +1,21 @@
use axum::extract::State;
use ruma::api::client::{
error::ErrorKind,
filter::{create_filter, get_filter},
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
///
/// Loads a filter that was previously created.
///
/// - 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 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."));
};
@@ -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`
///
/// 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");
Ok(create_filter::v3::Response::new(
services().users.create_filter(sender_user, &body.filter)?,
services.users.create_filter(sender_user, &body.filter)?,
))
}
+51 -46
View File
@@ -3,7 +3,8 @@ use std::{
time::Instant,
};
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Error, Result};
use axum::extract::State;
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
@@ -18,11 +19,9 @@ use ruma::{
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
};
use serde_json::json;
use service::user_is_local;
use tracing::debug;
use super::SESSION_ID_LENGTH;
use crate::{services, Ruma};
use crate::{service::Services, Ruma};
/// # `POST /_matrix/client/r0/keys/upload`
///
@@ -31,12 +30,14 @@ use crate::{services, Ruma};
/// - Adds one time keys
/// - If there are no device keys yet: Adds device keys (TODO: merge with
/// 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_device = body.sender_device.as_ref().expect("user is authenticated");
for (key_key, key_value) in &body.one_time_keys {
services()
services
.users
.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 {
// TODO: merge this and the existing event?
// This check is needed to assure that signatures are kept
if services()
if services
.users
.get_device_keys(sender_user, sender_device)?
.is_none()
{
services()
services
.users
.add_device_keys(sender_user, sender_device, device_keys)?;
}
}
Ok(upload_keys::v3::Response {
one_time_key_counts: services()
one_time_key_counts: services
.users
.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.
/// - The master and self-signing keys contain signatures that the user is
/// 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");
get_keys_helper(
&services,
Some(sender_user),
&body.device_keys,
|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`
///
/// Claims one-time keys
pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Result<claim_keys::v3::Response> {
claim_keys_helper(&body.one_time_keys).await
pub(crate) async fn claim_keys_route(
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`
@@ -95,7 +101,7 @@ pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Res
///
/// - Requires UIAA to verify password
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> {
let sender_user = body.sender_user.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 {
let (worked, uiaainfo) = services()
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
if !worked {
@@ -121,7 +127,7 @@ pub(crate) async fn upload_signing_keys_route(
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
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 {
services().users.add_cross_signing_keys(
services.users.add_cross_signing_keys(
sender_user,
master_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.
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> {
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."))?
.to_owned(),
);
services()
services
.users
.sign_key(user_id, key_id, signature, sender_user)?;
}
@@ -192,14 +198,14 @@ pub(crate) async fn upload_signatures_route(
///
/// - TODO: left users
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device_list_updates = HashSet::new();
device_list_updates.extend(
services()
services
.users
.keys_changed(
sender_user.as_str(),
@@ -215,14 +221,14 @@ pub(crate) async fn get_key_changes_route(
.filter_map(Result::ok),
);
for room_id in services()
for room_id in services
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
{
device_list_updates.extend(
services()
services
.users
.keys_changed(
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>(
sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>, allowed_signatures: F,
include_display_names: bool,
services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
allowed_signatures: F, include_display_names: bool,
) -> Result<get_keys::v3::Response> {
let mut master_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 {
let user_id: &UserId = user_id;
if !user_is_local(user_id) {
if !services.globals.user_is_local(user_id) {
get_over_federation
.entry(user_id.server_name())
.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() {
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?;
if let Some(mut keys) = services().users.get_device_keys(user_id, &device_id)? {
let metadata = services()
if let Some(mut keys) = services.users.get_device_keys(user_id, &device_id)? {
let metadata = services
.users
.get_device_metadata(user_id, &device_id)?
.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 {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = services().users.get_device_keys(user_id, device_id)? {
let metadata = services()
if let Some(mut keys) = services.users.get_device_keys(user_id, device_id)? {
let metadata = services
.users
.get_device_metadata(user_id, device_id)?
.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
.get_master_key(sender_user, user_id, &allowed_signatures)?
{
master_keys.insert(user_id.to_owned(), master_key);
}
if let Some(self_signing_key) =
services()
services
.users
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
{
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
}
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);
}
}
@@ -326,7 +332,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let mut failures = BTreeMap::new();
let back_off = |id| async {
match services()
match services
.globals
.bad_query_ratelimiter
.write()
@@ -345,7 +351,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter()
.map(|(server, vec)| async move {
if let Some((time, tries)) = services()
if let Some((time, tries)) = services
.globals
.bad_query_ratelimiter
.read()
@@ -356,8 +362,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
const MIN: u64 = 5 * 60;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off query from {server:?}");
return (server, Err(Error::BadServerResponse("bad query, still backing off")));
return (server, Err!(BadServerResponse("bad query from {server:?}, still backing off")));
}
}
@@ -369,7 +374,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
let request = federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed,
};
let response = services()
let response = services
.sending
.send_federation_request(server, request)
.await;
@@ -381,19 +386,19 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
while let Some((server, response)) = futures.next().await {
if let Ok(Ok(response)) = response {
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) =
services()
services
.users
.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);
}
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");
services().users.add_cross_signing_keys(
services.users.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key request resulting in an
* endless loop */
@@ -444,14 +449,14 @@ fn add_unsigned_device_display_name(
}
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> {
let mut one_time_keys = BTreeMap::new();
let mut get_over_federation = BTreeMap::new();
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
.entry(user_id.server_name())
.or_insert_with(Vec::new)
@@ -460,7 +465,7 @@ pub(crate) async fn claim_keys_helper(
let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map {
if let Some(one_time_keys) = services()
if let Some(one_time_keys) = services
.users
.take_one_time_key(user_id, device_id, key_algorithm)?
{
@@ -483,7 +488,7 @@ pub(crate) async fn claim_keys_helper(
}
(
server,
services()
services
.sending
.send_federation_request(
server,
+81 -334
View File
@@ -1,37 +1,27 @@
#![allow(deprecated)]
use std::{io::Cursor, sync::Arc, time::Duration};
use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{debug, error, utils::math::ruma_from_usize, warn};
use image::io::Reader as ImgReader;
use ipaddress::IPAddress;
use reqwest::Url;
use ruma::api::client::{
error::{ErrorKind, RetryAfter},
media::{
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview,
},
};
use webpage::HTML;
use crate::{
debug_warn,
service::{
media::{FileMeta, UrlPreviewData},
server_is_ours,
},
services,
use conduit::{
debug_info, debug_warn, err, info,
utils::{
self,
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
math::ruma_from_usize,
},
Error, Result, Ruma, RumaResponse,
warn, Err, Error, Result,
};
use ruma::api::client::media::{
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config, get_media_preview,
};
use service::{
media::{FileMeta, MXC_LENGTH},
Services,
};
/// generated MXC ID (`media-id`) length
const MXC_LENGTH: usize = 32;
use crate::{Ruma, RumaResponse};
/// Cache control for immutable objects
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
@@ -42,10 +32,10 @@ const CORP_CROSS_ORIGIN: &str = "cross-origin";
///
/// Returns max upload size.
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> {
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 +47,11 @@ pub(crate) async fn get_media_config_route(
///
/// Returns max upload size.
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>> {
get_media_config_route(body).await.map(RumaResponse)
get_media_config_route(State(services), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/preview_url`
@@ -67,41 +59,29 @@ pub(crate) async fn get_media_config_v1_route(
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url;
if !url_preview_allowed(url) {
warn!(%sender_user, "URL is not allowed to be previewed: {url}");
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed"));
if !services.media.url_preview_allowed(url) {
debug_info!(%sender_user, %url, "URL is not allowed to be previewed");
return Err!(Request(Forbidden("URL is not allowed to be previewed")));
}
match get_url_preview(url).await {
match services.media.get_url_preview(url).await {
Ok(preview) => {
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}");
Error::BadRequest(
ErrorKind::LimitExceeded {
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
},
"Failed to generate a URL preview, try again later.",
)
warn!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
err!(Request(Unknown("Failed to generate a URL preview")))
})?;
Ok(get_media_preview::v3::Response::from_raw_value(res))
},
Err(e) => {
warn!(%sender_user, "Failed to generate a URL preview: {e}");
// 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.",
))
info!(%sender_user, "Failed to generate a URL preview: {e}");
Err!(Request(Unknown("Failed to generate a URL preview")))
},
}
}
@@ -115,9 +95,10 @@ pub(crate) async fn get_media_preview_route(
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
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>> {
get_media_preview_route(InsecureClientIp(client), body)
get_media_preview_route(State(services), InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
@@ -130,17 +111,14 @@ pub(crate) async fn get_media_preview_v1_route(
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!(
"mxc://{}/{}",
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
let mxc = format!("mxc://{}/{}", services.globals.server_name(), utils::random_string(MXC_LENGTH));
services()
services
.media
.create(
Some(sender_user.clone()),
@@ -178,9 +156,10 @@ pub(crate) async fn create_content_route(
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
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>> {
create_content_route(InsecureClientIp(client), body)
create_content_route(State(services), InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
@@ -195,7 +174,8 @@ pub(crate) async fn create_content_v1_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
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> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -203,7 +183,7 @@ pub(crate) async fn get_content_route(
content,
content_type,
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 file = content.expect("content");
@@ -215,8 +195,9 @@ pub(crate) async fn get_content_route(
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
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(
&services,
&mxc,
&body.server_name,
body.media_id.clone(),
@@ -224,10 +205,7 @@ pub(crate) async fn get_content_route(
body.timeout_ms,
)
.await
.map_err(|e| {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Error::BadRequest(ErrorKind::NotFound, "Remote media error.")
})?;
.map_err(|e| err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))))?;
let content_disposition = Some(make_content_disposition(
&response.content_type,
@@ -243,7 +221,7 @@ pub(crate) async fn get_content_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
})
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
Err!(Request(NotFound("Media not found.")))
}
}
@@ -261,9 +239,10 @@ pub(crate) async fn get_content_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
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>> {
get_content_route(InsecureClientIp(client), body)
get_content_route(State(services), InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
@@ -278,7 +257,8 @@ pub(crate) async fn get_content_v1_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
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> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -286,7 +266,7 @@ pub(crate) async fn get_content_as_filename_route(
content,
content_type,
content_disposition,
}) = services().media.get(&mxc).await?
}) = services.media.get(&mxc).await?
{
let content_disposition = Some(make_content_disposition(
&content_type,
@@ -302,8 +282,9 @@ pub(crate) async fn get_content_as_filename_route(
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
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(
&services,
&mxc,
&body.server_name,
body.media_id.clone(),
@@ -327,13 +308,10 @@ pub(crate) async fn get_content_as_filename_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
})
},
Err(e) => {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
},
Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
}
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
Err!(Request(NotFound("Media not found.")))
}
}
@@ -351,9 +329,10 @@ pub(crate) async fn get_content_as_filename_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
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>> {
get_content_as_filename_route(InsecureClientIp(client), body)
get_content_as_filename_route(State(services), InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
@@ -368,7 +347,8 @@ pub(crate) async fn get_content_as_filename_v1_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
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> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
@@ -376,16 +356,16 @@ pub(crate) async fn get_content_thumbnail_route(
content,
content_type,
content_disposition,
}) = services()
}) = services
.media
.get_thumbnail(
&mxc,
body.width
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?,
body.height
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?,
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?,
)
.await?
{
@@ -399,8 +379,8 @@ pub(crate) async fn get_content_thumbnail_route(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
content_disposition,
})
} else if !server_is_ours(&body.server_name) && body.allow_remote {
if services()
} else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
if services
.globals
.prevent_media_downloads_from()
.contains(&body.server_name)
@@ -408,10 +388,10 @@ pub(crate) async fn get_content_thumbnail_route(
// we'll lie to the client and say the blocked server's media was not found and
// log. the client has no way of telling anyways so this is a security bonus.
debug_warn!("Received request for media `{}` on blocklisted server", mxc);
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
return Err!(Request(NotFound("Media not found.")));
}
match services()
match services
.sending
.send_federation_request(
&body.server_name,
@@ -430,7 +410,7 @@ pub(crate) async fn get_content_thumbnail_route(
.await
{
Ok(get_thumbnail_response) => {
services()
services
.media
.upload_thumbnail(
None,
@@ -457,13 +437,10 @@ pub(crate) async fn get_content_thumbnail_route(
content_disposition,
})
},
Err(e) => {
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
},
Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
}
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
Err!(Request(NotFound("Media not found.")))
}
}
@@ -481,17 +458,19 @@ pub(crate) async fn get_content_thumbnail_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
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>> {
get_content_thumbnail_route(InsecureClientIp(client), body)
get_content_thumbnail_route(State(services), InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
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> {
if services()
if services
.globals
.prevent_media_downloads_from()
.contains(&server_name.to_owned())
@@ -499,10 +478,10 @@ async fn get_remote_content(
// we'll lie to the client and say the blocked server's media was not found and
// log. the client has no way of telling anyways so this is a security bonus.
debug_warn!("Received request for media `{mxc}` on blocklisted server");
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
return Err!(Request(NotFound("Media not found.")));
}
let content_response = services()
let content_response = services
.sending
.send_federation_request(
server_name,
@@ -522,7 +501,7 @@ async fn get_remote_content(
None,
));
services()
services
.media
.create(
None,
@@ -541,235 +520,3 @@ async fn get_remote_content(
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
})
}
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let image = client.get(url).send().await?.bytes().await?;
let mxc = format!(
"mxc://{}/{}",
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
services()
.media
.create(None, &mxc, None, None, &image)
.await?;
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
Err(_) => (None, None),
Ok(reader) => match reader.into_dimensions() {
Err(_) => (None, None),
Ok((width, height)) => (Some(width), Some(height)),
},
};
Ok(UrlPreviewData {
image: Some(mxc),
image_size: Some(image.len()),
image_width: width,
image_height: height,
..Default::default()
})
}
async fn download_html(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
let mut response = client.get(url).send().await?;
let mut bytes: Vec<u8> = Vec::new();
while let Some(chunk) = response.chunk().await? {
bytes.extend_from_slice(&chunk);
if bytes.len() > services().globals.url_preview_max_spider_size() {
debug!(
"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.",
url,
services().globals.url_preview_max_spider_size()
);
break;
}
}
let body = String::from_utf8_lossy(&bytes);
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
return Err(Error::BadRequest(ErrorKind::Unknown, "Failed to parse HTML"));
};
let mut data = match html.opengraph.images.first() {
None => UrlPreviewData::default(),
Some(obj) => download_image(client, &obj.url).await?,
};
let props = html.opengraph.properties;
/* use OpenGraph title/description, but fall back to HTML if not available */
data.title = props.get("title").cloned().or(html.title);
data.description = props.get("description").cloned().or(html.description);
Ok(data)
}
async fn request_url_preview(url: &str) -> Result<UrlPreviewData> {
if let Ok(ip) = IPAddress::parse(url) {
if !services().globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
}
}
let client = &services().globals.client.url_preview;
let response = client.head(url).send().await?;
if let Some(remote_addr) = response.remote_addr() {
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
if !services().globals.valid_cidr_range(&ip) {
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
}
}
}
let Some(content_type) = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|x| x.to_str().ok())
else {
return Err(Error::BadRequest(ErrorKind::Unknown, "Unknown Content-Type"));
};
let data = match content_type {
html if html.starts_with("text/html") => download_html(client, url).await?,
img if img.starts_with("image/") => download_image(client, url).await?,
_ => return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported Content-Type")),
};
services().media.set_url_preview(url, &data).await?;
Ok(data)
}
async fn get_url_preview(url: &str) -> Result<UrlPreviewData> {
if let Some(preview) = services().media.get_url_preview(url).await {
return Ok(preview);
}
// ensure that only one request is made per URL
let mutex_request = Arc::clone(
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 {
Some(preview) => Ok(preview),
None => request_url_preview(url).await,
}
}
fn url_preview_allowed(url_str: &str) -> bool {
let url: Url = match Url::parse(url_str) {
Ok(u) => u,
Err(e) => {
warn!("Failed to parse URL from a str: {}", e);
return false;
},
};
if ["http", "https"]
.iter()
.all(|&scheme| scheme != url.scheme().to_lowercase())
{
debug!("Ignoring non-HTTP/HTTPS URL to preview: {}", url);
return false;
}
let host = match url.host_str() {
None => {
debug!("Ignoring URL preview for a URL that does not have a host (?): {}", url);
return false;
},
Some(h) => h.to_owned(),
};
let allowlist_domain_contains = services().globals.url_preview_domain_contains_allowlist();
let allowlist_domain_explicit = services().globals.url_preview_domain_explicit_allowlist();
let denylist_domain_explicit = services().globals.url_preview_domain_explicit_denylist();
let allowlist_url_contains = services().globals.url_preview_url_contains_allowlist();
if allowlist_domain_contains.contains(&"*".to_owned())
|| allowlist_domain_explicit.contains(&"*".to_owned())
|| allowlist_url_contains.contains(&"*".to_owned())
{
debug!("Config key contains * which is allowing all URL previews. Allowing URL {}", url);
return true;
}
if !host.is_empty() {
if denylist_domain_explicit.contains(&host) {
debug!(
"Host {} is not allowed by url_preview_domain_explicit_denylist (check 1/4)",
&host
);
return false;
}
if allowlist_domain_explicit.contains(&host) {
debug!("Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)", &host);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&host.clone()))
{
debug!("Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)", &host);
return true;
}
if allowlist_url_contains
.iter()
.any(|url_s| url.to_string().contains(&url_s.to_string()))
{
debug!("URL {} is allowed by url_preview_url_contains_allowlist (check 4/4)", &host);
return true;
}
// check root domain if available and if user has root domain checks
if services().globals.url_preview_check_root_domain() {
debug!("Checking root domain");
match host.split_once('.') {
None => return false,
Some((_, root_domain)) => {
if denylist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is not allowed by url_preview_domain_explicit_denylist (check 1/3)",
&root_domain
);
return true;
}
if allowlist_domain_explicit.contains(&root_domain.to_owned()) {
debug!(
"Root domain {} is allowed by url_preview_domain_explicit_allowlist (check 2/3)",
&root_domain
);
return true;
}
if allowlist_domain_contains
.iter()
.any(|domain_s| domain_s.contains(&root_domain.to_owned()))
{
debug!(
"Root domain {} is allowed by url_preview_domain_contains_allowlist (check 3/3)",
&root_domain
);
return true;
}
},
}
}
}
false
}
File diff suppressed because it is too large Load Diff
+35 -27
View File
@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashSet};
use axum::extract::State;
use conduit::PduCount;
use ruma::{
api::client::{
@@ -12,7 +13,10 @@ use ruma::{
};
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}`
///
@@ -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
/// allowed
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
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
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"));
}
if body.event_type == MessageLikeEventType::CallInvite
&& services().rooms.directory.is_public_room(&body.room_id)?
{
if body.event_type == MessageLikeEventType::CallInvite && services.rooms.directory.is_public_room(&body.room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"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
if let Some(response) = services()
if let Some(response) = services
.transaction_ids
.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();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let event_id = services()
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
@@ -82,6 +84,11 @@ pub(crate) async fn send_message_event_route(
unsigned: Some(unsigned),
state_key: None,
redacts: None,
timestamp: if body.appservice_info.is_some() {
body.timestamp
} else {
None
},
},
sender_user,
&body.room_id,
@@ -89,7 +96,7 @@ pub(crate) async fn send_message_event_route(
)
.await?;
services()
services
.transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?;
@@ -105,7 +112,7 @@ pub(crate) async fn send_message_event_route(
/// - Only works if the user is joined (TODO: always allow, but only show events
/// where the user was joined, depending on `history_visibility`)
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -123,7 +130,7 @@ pub(crate) async fn get_message_events_route(
.as_ref()
.and_then(|t| PduCount::try_from_string(t).ok());
services()
services
.rooms
.lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
@@ -139,12 +146,12 @@ pub(crate) async fn get_message_events_route(
match body.dir {
ruma::api::Direction::Forward => {
let events_after: Vec<_> = services()
let events_after: Vec<_> = services
.rooms
.timeline
.pdus_after(sender_user, &body.room_id, from)?
.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`
@@ -157,7 +164,7 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034
*/
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_device,
&body.room_id,
@@ -181,17 +188,17 @@ pub(crate) async fn get_message_events_route(
resp.chunk = events_after;
},
ruma::api::Direction::Backward => {
services()
services
.rooms
.timeline
.backfill_if_required(&body.room_id, from)
.await?;
let events_before: Vec<_> = services()
let events_before: Vec<_> = services
.rooms
.timeline
.pdus_until(sender_user, &body.room_id, from)?
.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(limit)
.collect();
@@ -202,7 +209,7 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034
*/
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_device,
&body.room_id,
@@ -229,11 +236,12 @@ pub(crate) async fn get_message_events_route(
resp.state = Vec::new();
for ll_id in &lazy_loaded {
if let Some(member_event) = services().rooms.state_accessor.room_state_get(
&body.room_id,
&StateEventType::RoomMember,
ll_id.as_str(),
)? {
if let Some(member_event) =
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomMember, ll_id.as_str())?
{
resp.state.push(member_event.to_state_event());
}
}
@@ -241,7 +249,7 @@ pub(crate) async fn get_message_events_route(
// remove the feature check when we are sure clients like element can handle it
if !cfg!(feature = "element_hacks") {
if let Some(next_token) = next_token {
services()
services
.rooms
.lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token)
@@ -252,8 +260,8 @@ pub(crate) async fn get_message_events_route(
Ok(resp)
}
fn visibility_filter(pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
services()
fn visibility_filter(services: &Services, pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
services
.rooms
.state_accessor
.user_can_see_event(user_id, room_id, &pdu.event_id)
+5 -4
View File
@@ -1,5 +1,6 @@
use std::time::Duration;
use axum::extract::State;
use conduit::utils;
use ruma::{
api::client::{account, error::ErrorKind},
@@ -7,7 +8,7 @@ use ruma::{
};
use super::TOKEN_LENGTH;
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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
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> {
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 expires_in = services()
let expires_in = services
.users
.create_openid_token(&body.user_id, &access_token)?;
Ok(account::request_openid_token::v3::Response {
access_token,
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),
})
}
+20 -8
View File
@@ -1,22 +1,32 @@
use std::time::Duration;
use axum::extract::State;
use ruma::api::client::{
error::ErrorKind,
presence::{get_presence, set_presence},
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
///
/// 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> {
if !services().globals.allow_local_presence() {
pub(crate) async fn set_presence_route(
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"));
}
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
.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.
///
/// - 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> {
if !services().globals.allow_local_presence() {
pub(crate) async fn get_presence_route(
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"));
}
@@ -37,12 +49,12 @@ pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) ->
let mut presence_event = None;
for _room_id in services()
for _room_id in services
.rooms
.user
.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);
break;
}
+79 -59
View File
@@ -1,3 +1,5 @@
use axum::extract::State;
use conduit::{pdu::PduBuilder, warn, Error, Result};
use ruma::{
api::{
client::{
@@ -11,12 +13,9 @@ use ruma::{
OwnedMxcUri, OwnedRoomId, OwnedUserId,
};
use serde_json::value::to_raw_value;
use tracing::warn;
use service::Services;
use crate::{
service::{pdu::PduBuilder, user_is_local},
services, Error, Result, Ruma,
};
use crate::Ruma;
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
///
@@ -24,21 +23,21 @@ use crate::{
///
/// - Also makes sure other users receive the update using presence EDUs
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> {
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
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
.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
services()
services
.presence
.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
/// displayname over federation
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> {
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
if let Ok(response) = services()
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
@@ -68,19 +67,19 @@ pub(crate) async fn get_displayname_route(
)
.await
{
if !services().users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?;
if !services.users.exists(&body.user_id)? {
services.users.create(&body.user_id, None)?;
}
services()
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
services()
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
services()
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.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
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
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
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> {
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
.state_cache
.rooms_joined(sender_user)
@@ -119,6 +118,7 @@ pub(crate) async fn set_avatar_url_route(
.collect();
update_avatar_url(
&services,
sender_user.clone(),
body.avatar_url.clone(),
body.blurhash.clone(),
@@ -126,9 +126,9 @@ pub(crate) async fn set_avatar_url_route(
)
.await?;
if services().globals.allow_local_presence() {
if services.globals.allow_local_presence() {
// Presence update
services()
services
.presence
.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
/// `avatar_url` and blurhash over federation
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> {
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
if let Ok(response) = services()
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
@@ -158,19 +158,19 @@ pub(crate) async fn get_avatar_url_route(
)
.await
{
if !services().users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?;
if !services.users.exists(&body.user_id)? {
services.users.create(&body.user_id, None)?;
}
services()
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
services()
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
services()
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.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
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_avatar_url::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?,
avatar_url: services.users.avatar_url(&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,
/// fetch profile over federation.
pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> Result<get_profile::v3::Response> {
if !user_is_local(&body.user_id) {
pub(crate) async fn get_profile_route(
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
if let Ok(response) = services()
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
@@ -214,19 +216,19 @@ pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> R
)
.await
{
if !services().users.exists(&body.user_id)? {
services().users.create(&body.user_id, None)?;
if !services.users.exists(&body.user_id)? {
services.users.create(&body.user_id, None)?;
}
services()
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
services()
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
services()
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
@@ -239,23 +241,29 @@ 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
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_profile::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?,
displayname: services().users.displayname(&body.user_id)?,
avatar_url: services.users.avatar_url(&body.user_id)?,
blurhash: services.users.blurhash(&body.user_id)?,
displayname: services.users.displayname(&body.user_id)?,
})
}
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<()> {
services()
let current_display_name = services.users.displayname(&user_id).unwrap_or_default();
if displayname == current_display_name {
return Ok(());
}
services
.users
.set_displayname(&user_id, displayname.clone())
.await?;
@@ -271,7 +279,7 @@ pub async fn update_displayname(
displayname: displayname.clone(),
join_authorized_via_users_server: None,
..serde_json::from_str(
services()
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
@@ -287,6 +295,7 @@ pub async fn update_displayname(
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
},
room_id,
))
@@ -294,19 +303,27 @@ pub async fn update_displayname(
.filter_map(Result::ok)
.collect();
update_all_rooms(all_joined_rooms, user_id).await;
update_all_rooms(services, all_joined_rooms, user_id).await;
Ok(())
}
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<()> {
services()
let current_avatar_url = services.users.avatar_url(&user_id).unwrap_or_default();
let current_blurhash = services.users.blurhash(&user_id).unwrap_or_default();
if current_avatar_url == avatar_url && current_blurhash == blurhash {
return Ok(());
}
services
.users
.set_avatar_url(&user_id, avatar_url.clone())
.await?;
services()
services
.users
.set_blurhash(&user_id, blurhash.clone())
.await?;
@@ -323,7 +340,7 @@ pub async fn update_avatar_url(
blurhash: blurhash.clone(),
join_authorized_via_users_server: None,
..serde_json::from_str(
services()
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
@@ -339,6 +356,7 @@ pub async fn update_avatar_url(
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
},
room_id,
))
@@ -346,15 +364,17 @@ pub async fn update_avatar_url(
.filter_map(Result::ok)
.collect();
update_all_rooms(all_joined_rooms, user_id).await;
update_all_rooms(services, all_joined_rooms, user_id).await;
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 {
let state_lock = services().rooms.state.mutex.lock(room_id).await;
if let Err(e) = services()
let state_lock = services.rooms.state.mutex.lock(room_id).await;
if let Err(e) = services
.rooms
.timeline
.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::{
api::client::{
error::ErrorKind,
@@ -10,18 +11,18 @@ use ruma::{
push::{InsertPushRuleError, RemovePushRuleError, Ruleset},
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `GET /_matrix/client/r0/pushrules/`
///
/// Retrieves the push rules event for this user.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event =
services()
services
.account_data
.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,
})
} else {
services().account_data.update(
services.account_data.update(
None,
sender_user,
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}`
///
/// 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 event = services()
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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}`
///
/// 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 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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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);
}
services().account_data.update(
services.account_data.update(
None,
sender_user,
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.
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> {
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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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.
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> {
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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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."));
}
services().account_data.update(
services.account_data.update(
None,
sender_user,
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.
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> {
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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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.
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> {
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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.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."));
}
services().account_data.update(
services.account_data.update(
None,
sender_user,
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.
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> {
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
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
@@ -336,7 +341,7 @@ pub(crate) async fn delete_pushrule_route(
return Err(err);
}
services().account_data.update(
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
@@ -349,11 +354,13 @@ pub(crate) async fn delete_pushrule_route(
/// # `GET /_matrix/client/r0/pushers`
///
/// 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");
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.
///
/// - 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");
services().pusher.set_pusher(sender_user, &body.action)?;
services.pusher.set_pusher(sender_user, &body.action)?;
Ok(set_pusher::v3::Response::default())
}
+14 -13
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::PduCount;
use ruma::{
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
@@ -10,7 +11,7 @@ use ruma::{
MilliSecondsSinceUnixEpoch,
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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
/// EDU
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> {
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(),
},
};
services().account_data.update(
services.account_data.update(
Some(&body.room_id),
sender_user,
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() {
services()
services
.rooms
.user
.reset_notification_counts(sender_user, &body.room_id)?;
}
if let Some(event) = &body.private_read_receipt {
let count = services()
let count = services
.rooms
.timeline
.get_pdu_count(event)?
@@ -60,7 +61,7 @@ pub(crate) async fn set_read_marker_route(
},
PduCount::Normal(c) => c,
};
services()
services
.rooms
.read_receipt
.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();
receipt_content.insert(event.to_owned(), receipts);
services().rooms.read_receipt.readreceipt_update(
services.rooms.read_receipt.readreceipt_update(
sender_user,
&body.room_id,
&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.
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> {
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,
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
) {
services()
services
.rooms
.user
.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(),
},
};
services().account_data.update(
services.account_data.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::FullyRead,
@@ -142,7 +143,7 @@ pub(crate) async fn create_receipt_route(
let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.clone(), receipts);
services().rooms.read_receipt.readreceipt_update(
services.rooms.read_receipt.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
@@ -152,7 +153,7 @@ pub(crate) async fn create_receipt_route(
)?;
},
create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services()
let count = services
.rooms
.timeline
.get_pdu_count(&body.event_id)?
@@ -166,7 +167,7 @@ pub(crate) async fn create_receipt_route(
},
PduCount::Normal(c) => c,
};
services()
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count)?;
+8 -4
View File
@@ -1,23 +1,26 @@
use axum::extract::State;
use ruma::{
api::client::redact::redact_event,
events::{room::redaction::RoomRedactionEventContent, TimelineEventType},
};
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}`
///
/// Tries to send a redaction event into the room.
///
/// - 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 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
.timeline
.build_and_append_pdu(
@@ -31,6 +34,7 @@ pub(crate) async fn redact_event_route(body: Ruma<redact_event::v3::Request>) ->
unsigned: None,
state_key: None,
redacts: Some(body.event_id.into()),
timestamp: None,
},
sender_user,
&body.room_id,
+41 -49
View File
@@ -1,30 +1,28 @@
use axum::extract::State;
use ruma::api::client::relations::{
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}`
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let res = services()
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&Some(body.event_type.clone()),
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&Some(body.event_type.clone()),
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
Ok(get_relating_events_with_rel_type_and_event_type::v1::Response {
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}`
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let res = services()
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
Ok(get_relating_events_with_rel_type::v1::Response {
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}`
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&None,
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)
services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&None,
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)
}
+22 -8
View File
@@ -1,5 +1,6 @@
use std::time::Duration;
use axum::extract::State;
use rand::Rng;
use ruma::{
api::client::{error::ErrorKind, room::report_content},
@@ -9,13 +10,18 @@ use ruma::{
use tokio::time::sleep;
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}`
///
/// Reports an inappropriate event to homeserver admins
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> {
// user authentication
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
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(
ErrorKind::NotFound,
"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
// urgency
services()
services
.admin
.send_message(message::RoomMessageEventContent::text_html(
format!(
@@ -79,8 +93,8 @@ pub(crate) async fn report_event_route(
/// check if score is in valid range
/// check if report reasoning is less than or equal to 750 characters
fn is_report_valid(
event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>, score: Option<ruma::Int>,
pdu: &std::sync::Arc<PduEvent>,
services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>,
score: Option<ruma::Int>, pdu: &std::sync::Arc<PduEvent>,
) -> Result<bool> {
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
.state_cache
.room_members(&pdu.room_id)
+89 -71
View File
@@ -1,6 +1,7 @@
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::{
api::client::{
error::ErrorKind,
@@ -30,8 +31,8 @@ use tracing::{error, info, warn};
use super::invite_helper;
use crate::{
service::{appservice::RegistrationInfo, pdu::PduBuilder},
services, Error, Result, Ruma,
service::{appservice::RegistrationInfo, pdu::PduBuilder, Services},
Error, Result, Ruma,
};
/// Recommended transferable state events list from the spec
@@ -63,44 +64,46 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
/// - Send events listed in initial state
/// - Send events implied by `name` and `topic`
/// - 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;
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()
&& !services().users.is_admin(sender_user)?
&& !services.users.is_admin(sender_user)?
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled."));
}
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 {
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
if services().rooms.short.get_shortroomid(&room_id)?.is_some() {
if services.rooms.short.get_shortroomid(&room_id)?.is_some() {
return Err(Error::BadRequest(
ErrorKind::RoomInUse,
"Room with that custom room ID already exists",
));
}
let _short_id = services().rooms.short.get_or_create_shortroomid(&room_id)?;
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
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 {
None
};
let room_version = match body.room_version.clone() {
Some(room_version) => {
if services()
if services
.globals
.supported_room_versions()
.contains(&room_version)
@@ -113,7 +116,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 {
@@ -184,7 +187,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
};
// 1. The room create event
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -194,6 +197,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -202,7 +206,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?;
// 2. Let the room creator join
services()
services
.rooms
.timeline
.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,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services().users.displayname(sender_user)?,
avatar_url: services().users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
is_direct: Some(body.is_direct),
third_party_invite: None,
blurhash: services().users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user)?,
reason: None,
join_authorized_via_users_server: None,
})
@@ -222,6 +226,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(sender_user.to_string()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -249,7 +254,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
let power_levels_content =
default_power_levels_content(&body.power_level_content_override, &body.visibility, users)?;
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -259,6 +264,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -268,7 +274,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 4. Canonical room alias
if let Some(room_alias_id) = &alias {
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -282,6 +288,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -293,7 +300,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 5. Events set by preset
// 5.1 Join Rules
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -308,6 +315,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -316,7 +324,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?;
// 5.2 History Visibility
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -327,6 +335,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -335,7 +344,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
.await?;
// 5.3 Guest Access
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -349,6 +358,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -378,11 +388,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);
// 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;
}
services()
services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
@@ -391,7 +401,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 7. Events implied by name and topic
if let Some(name) = &body.name {
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -402,6 +412,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -411,7 +422,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
}
if let Some(topic) = &body.topic {
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -424,6 +435,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&room_id,
@@ -435,21 +447,21 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
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");
}
}
// Homeserver specific stuff
if let Some(alias) = alias {
services()
services
.rooms
.alias
.set_alias(&alias, &room_id, sender_user)?;
}
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}");
@@ -464,20 +476,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
/// visibility)
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services()
let event = services
.rooms
.timeline
.get_pdu(&body.event_id)?
.ok_or_else(|| {
warn!("Event not found, event ID: {:?}", &body.event_id);
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
})?;
.ok_or_else(|| err!(Request(NotFound("Event {} not found.", &body.event_id))))?;
if !services()
if !services
.rooms
.state_accessor
.user_can_see_event(sender_user, &event.room_id, &body.event_id)?
@@ -502,10 +511,12 @@ pub(crate) async fn get_room_event_route(
///
/// - Only users joined to the room are allowed to call this, or if
/// `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");
if !services()
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
@@ -517,7 +528,7 @@ pub(crate) async fn get_room_aliases_route(body: Ruma<aliases::v3::Request>) ->
}
Ok(aliases::v3::Response {
aliases: services()
aliases: services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
@@ -536,10 +547,12 @@ pub(crate) async fn get_room_aliases_route(body: Ruma<aliases::v3::Request>) ->
/// - Transfers some state events
/// - Moves local aliases
/// - 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");
if !services()
if !services
.globals
.supported_room_versions()
.contains(&body.new_version)
@@ -551,19 +564,19 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
}
// 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
.short
.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
// intended to be used any further Fail if the sender does not have the required
// permissions
let tombstone_event_id = services()
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
@@ -577,6 +590,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&body.room_id,
@@ -586,11 +600,11 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Change lock to replacement room
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
let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
services()
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
@@ -658,7 +672,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
}
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -668,6 +682,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&replacement_room,
@@ -676,7 +691,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
.await?;
// Join the new room
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -684,11 +699,11 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services().users.displayname(sender_user)?,
avatar_url: services().users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
is_direct: None,
third_party_invite: None,
blurhash: services().users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user)?,
reason: None,
join_authorized_via_users_server: None,
})
@@ -696,6 +711,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
unsigned: None,
state_key: Some(sender_user.to_string()),
redacts: None,
timestamp: None,
},
sender_user,
&replacement_room,
@@ -705,7 +721,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS {
let event_content = match services()
let event_content = match services
.rooms
.state_accessor
.room_state_get(&body.room_id, event_type, "")?
@@ -714,7 +730,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
None => continue, // Skipping missing events.
};
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -724,6 +740,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&replacement_room,
@@ -733,13 +750,13 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
}
// Moves any local aliases to the new room
for alias in services()
for alias in services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.filter_map(Result::ok)
{
services()
services
.rooms
.alias
.set_alias(&alias, &replacement_room, sender_user)?;
@@ -747,7 +764,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Get the old room power levels
let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str(
services()
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
@@ -772,7 +789,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Modify the power levels in the old room to prevent sending of events and
// inviting new users
services()
services
.rooms
.timeline
.build_and_append_pdu(
@@ -782,6 +799,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
sender_user,
&body.room_id,
@@ -841,7 +859,7 @@ fn default_power_levels_content(
/// if a room is being created with a room alias, run our checks
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> {
// Basic checks on the room alias validity
if room_alias_name.contains(':') {
@@ -858,7 +876,7 @@ async fn room_alias_check(
}
// check if room alias is forbidden
if services()
if services
.globals
.forbidden_alias_names()
.is_match(room_alias_name)
@@ -866,13 +884,13 @@ async fn room_alias_check(
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| {
info!("Failed to parse room alias {room_alias_name}: {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.")
})?;
info!("Failed to parse room alias {room_alias_name}: {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.")
})?;
if services()
if services
.rooms
.alias
.resolve_local_alias(&full_room_alias)?
@@ -885,7 +903,7 @@ async fn room_alias_check(
if !info.aliases.is_match(full_room_alias.as_str()) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
}
} else if services()
} else if services
.appservice
.is_exclusive_alias(&full_room_alias)
.await
@@ -899,9 +917,9 @@ async fn room_alias_check(
}
/// 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
if services()
if services
.globals
.forbidden_alias_names()
.is_match(custom_room_id)
@@ -922,7 +940,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}");
+13 -18
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use axum::extract::State;
use ruma::{
api::client::{
error::ErrorKind,
@@ -14,7 +15,7 @@ use ruma::{
};
use tracing::debug;
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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
/// 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 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 room_ids = filter.rooms.clone().unwrap_or_else(|| {
services()
services
.rooms
.state_cache
.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) {
for room_id in &room_ids {
if !services()
.rooms
.state_cache
.is_joined(sender_user, room_id)?
{
if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"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
if services()
if services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, room_id)?
{
let room_state = services()
let room_state = services
.rooms
.state_accessor
.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();
for room_id in &room_ids {
if !services()
.rooms
.state_cache
.is_joined(sender_user, room_id)?
{
if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
));
}
if let Some(search) = services()
if let Some(search) = services
.rooms
.search
.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()
.skip(skip)
.filter_map(|result| {
services()
services
.rooms
.timeline
.get_pdu_from_id(result)
.ok()?
.filter(|pdu| {
!pdu.is_redacted()
&& services()
&& services
.rooms
.state_accessor
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
+39 -22
View File
@@ -1,3 +1,5 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use ruma::{
api::client::{
error::ErrorKind,
@@ -20,7 +22,7 @@ use serde::Deserialize;
use tracing::{debug, info, warn};
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)]
struct Claims {
@@ -32,8 +34,9 @@ struct Claims {
///
/// Get the supported login types of this server. One of these should be used as
/// the `type` field when logging in.
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn get_login_types_route(
_body: Ruma<get_login_types::v3::Request>,
InsecureClientIp(client): InsecureClientIp, _body: Ruma<get_login_types::v3::Request>,
) -> Result<get_login_types::v3::Response> {
Ok(get_login_types::v3::Response::new(vec![
get_login_types::v3::LoginType::Password(PasswordLoginType::default()),
@@ -55,7 +58,10 @@ pub(crate) async fn get_login_types_route(
/// Note: You can use [`GET
/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
/// 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
// TODO: Other login methods
let user_id = match &body.login_info {
@@ -68,7 +74,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
}) => {
debug!("Got password login type");
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 {
UserId::parse(user)
} else {
@@ -77,7 +83,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
}
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
let hash = services()
let hash = services
.users
.password_hash(&user_id)?
.ok_or(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."))?;
@@ -96,7 +102,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
token,
}) => {
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 =
jsonwebtoken::decode::<Claims>(token, jwt_decoding_key, &jsonwebtoken::Validation::default())
.map_err(|e| {
@@ -106,7 +112,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
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}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?
@@ -124,7 +130,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
}) => {
debug!("Got appservice login type");
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 {
UserId::parse(user)
} else {
@@ -164,22 +170,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
let device_exists = body.device_id.as_ref().map_or(false, |device_id| {
services()
services
.users
.all_device_ids(&user_id)
.any(|x| x.as_ref().map_or(false, |v| v == device_id))
});
if device_exists {
services().users.set_token(&user_id, &device_id, &token)?;
services.users.set_token(&user_id, &device_id, &token)?;
} else {
services()
.users
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
services.users.create_device(
&user_id,
&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
let client_discovery_info: Option<DiscoveryInfo> = services()
let client_discovery_info: Option<DiscoveryInfo> = services
.globals
.well_known_client()
.as_ref()
@@ -197,7 +207,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
device_id,
well_known: client_discovery_info,
expires_in: None,
home_server: Some(services().globals.server_name().to_owned()),
home_server: Some(services.globals.server_name().to_owned()),
refresh_token: None,
})
}
@@ -211,14 +221,17 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
/// last seen ts)
/// - Forgets to-device events
/// - 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_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
services().users.mark_device_key_update(sender_user)?;
services.users.mark_device_key_update(sender_user)?;
Ok(logout::v3::Response::new())
}
@@ -236,15 +249,19 @@ pub(crate) async fn logout_route(body: Ruma<logout::v3::Request>) -> Result<logo
/// Note: This is equivalent to calling [`GET
/// /_matrix/client/r0/logout`](fn.logout_route.html) from each device of this
/// 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");
for device_id in services().users.all_device_ids(sender_user).flatten() {
services().users.remove_device(sender_user, &device_id)?;
for device_id in services.users.all_device_ids(sender_user).flatten() {
services.users.remove_device(sender_user, &device_id)?;
}
// 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())
}
+6 -3
View File
@@ -1,17 +1,20 @@
use std::str::FromStr;
use axum::extract::State;
use ruma::{
api::client::{error::ErrorKind, space::get_hierarchy},
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`
///
/// Paginates over the space tree in a depth-first manner to locate child rooms
/// 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 limit = body
@@ -39,7 +42,7 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
}
}
services()
services
.rooms
.spaces
.get_client_hierarchy(
+37 -26
View File
@@ -1,6 +1,7 @@
use std::sync::Arc;
use conduit::{debug_info, error};
use axum::extract::State;
use conduit::{debug_info, error, pdu::PduBuilder, Error, Result};
use ruma::{
api::client::{
error::ErrorKind,
@@ -17,11 +18,9 @@ use ruma::{
serde::Raw,
EventId, RoomId, UserId,
};
use service::Services;
use crate::{
service::{pdu::PduBuilder, server_is_ours},
services, Error, Result, Ruma, RumaResponse,
};
use crate::{Ruma, RumaResponse};
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
///
@@ -32,17 +31,23 @@ use crate::{
/// allowed
/// - If event is new `canonical_alias`: Rejects if alias is incorrect
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(send_state_event::v3::Response {
event_id: send_state_event_for_key_helper(
&services,
sender_user,
&body.room_id,
&body.event_type,
&body.body.body,
body.state_key.clone(),
if body.appservice_info.is_some() {
body.timestamp
} else {
None
},
)
.await?
.into(),
@@ -58,9 +63,11 @@ pub(crate) async fn send_state_event_for_key_route(
/// allowed
/// - If event is new `canonical_alias`: Rejects if alias is incorrect
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>> {
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`
@@ -70,11 +77,11 @@ pub(crate) async fn send_state_event_for_empty_key_route(
/// - If not joined: Only works if current room history visibility is world
/// readable
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services()
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
@@ -86,7 +93,7 @@ pub(crate) async fn get_state_events_route(
}
Ok(get_state_events::v3::Response {
room_state: services()
room_state: services
.rooms
.state_accessor
.room_state_full(&body.room_id)
@@ -106,11 +113,11 @@ pub(crate) async fn get_state_events_route(
/// - If not joined: Only works if current room history visibility is world
/// readable
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services()
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
@@ -121,7 +128,7 @@ pub(crate) async fn get_state_events_for_key_route(
));
}
let event = services()
let event = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
@@ -161,17 +168,20 @@ pub(crate) async fn get_state_events_for_key_route(
/// - If not joined: Only works if current room history visibility is world
/// readable
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>> {
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(
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, timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
) -> Result<Arc<EventId>> {
allowed_to_send_state_event(room_id, event_type, json).await?;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let event_id = services()
allowed_to_send_state_event(services, room_id, event_type, json).await?;
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
@@ -181,6 +191,7 @@ async fn send_state_event_for_key_helper(
unsigned: None,
state_key: Some(state_key),
redacts: None,
timestamp,
},
sender,
room_id,
@@ -192,18 +203,18 @@ async fn send_state_event_for_key_helper(
}
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<()> {
match event_type {
// Forbid m.room.encryption if encryption is disabled
StateEventType::RoomEncryption => {
if !services().globals.allow_encryption() {
if !services.globals.allow_encryption() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
}
},
// admin room is a sensitive room, it should not ever be made public
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 let Ok(join_rule) = serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get()) {
if join_rule.join_rule == JoinRule::Public {
@@ -218,7 +229,7 @@ async fn allowed_to_send_state_event(
},
// admin room is a sensitive room, it should not ever be made world readable
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 let Ok(visibility_content) =
serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get())
@@ -243,8 +254,8 @@ async fn allowed_to_send_state_event(
}
for alias in aliases {
if !server_is_ours(alias.server_name())
|| services()
if !services.globals.server_is_ours(alias.server_name())
|| services
.rooms
.alias
.resolve_local_alias(&alias)?
+154 -142
View File
@@ -5,6 +5,7 @@ use std::{
time::Duration,
};
use axum::extract::State;
use conduit::{
error,
utils::math::{ruma_from_u64, ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
@@ -17,7 +18,7 @@ use ruma::{
self,
v3::{
Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom, LeftRoom, Presence,
RoomAccountData, RoomSummary, Rooms, State, Timeline, ToDevice,
RoomAccountData, RoomSummary, Rooms, State as RoomState, Timeline, ToDevice,
},
v4::SlidingOp,
DeviceLists, UnreadNotificationsCount,
@@ -34,7 +35,10 @@ use ruma::{
};
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`
///
@@ -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:
/// subset of the state at the point of the leave)
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>> {
let sender_user = body.sender_user.expect("user is authenticated");
let sender_device = body.sender_device.expect("user is authenticated");
let body = body.body;
// Presence update
if services().globals.allow_local_presence() {
services()
if services.globals.allow_local_presence() {
services
.presence
.ping_presence(&sender_user, &body.set_presence)?;
}
// 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_batch_string = next_batch.to_string();
@@ -96,17 +100,19 @@ pub(crate) async fn sync_events_route(
let filter = match body.filter {
None => FilterDefinition::default(),
Some(Filter::FilterDefinition(filter)) => filter,
Some(Filter::FilterId(filter_id)) => services()
Some(Filter::FilterId(filter_id)) => services
.users
.get_filter(&sender_user, &filter_id)?
.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 {
LazyLoadOptions::Enabled {
include_redundant_members: redundant,
} => (true, redundant),
LazyLoadOptions::Disabled => (false, false),
include_redundant_members,
} => (true, include_redundant_members),
LazyLoadOptions::Disabled => (false, cfg!(feature = "element_hacks")),
};
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
device_list_updates.extend(
services()
services
.users
.keys_changed(sender_user.as_ref(), since, None)
.filter_map(Result::ok),
);
if services().globals.allow_local_presence() {
process_presence_updates(&mut presence_updates, since, &sender_user).await?;
if services.globals.allow_local_presence() {
process_presence_updates(&services, &mut presence_updates, since, &sender_user).await?;
}
let all_joined_rooms = services()
let all_joined_rooms = services
.rooms
.state_cache
.rooms_joined(&sender_user)
.collect::<Vec<_>>();
// 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 {
let room_id = room_id?;
if let Ok(joined_room) = load_joined_room(
&services,
&sender_user,
&sender_device,
&room_id,
@@ -170,13 +177,14 @@ pub(crate) async fn sync_events_route(
}
let mut left_rooms = BTreeMap::new();
let all_left_rooms: Vec<_> = services()
let all_left_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_left(&sender_user)
.collect();
for result in all_left_rooms {
handle_left_room(
&services,
since,
&result?.0,
&sender_user,
@@ -190,7 +198,7 @@ pub(crate) async fn sync_events_route(
}
let mut invited_rooms = BTreeMap::new();
let all_invited_rooms: Vec<_> = services()
let all_invited_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_invited(&sender_user)
@@ -199,10 +207,10 @@ pub(crate) async fn sync_events_route(
let (room_id, invite_state_events) = result?;
// 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);
let invite_count = services()
let invite_count = services
.rooms
.state_cache
.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 {
let dont_share_encrypted_room = services()
let dont_share_encrypted_room = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(Result::ok)
.filter_map(|other_room_id| {
Some(
services()
services
.rooms
.state_accessor
.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*
services()
services
.users
.remove_to_device_events(&sender_user, &sender_device, since)?;
@@ -266,7 +274,7 @@ pub(crate) async fn sync_events_route(
.collect(),
},
account_data: GlobalAccountData {
events: services()
events: services
.account_data
.changes_since(None, &sender_user, since)?
.into_iter()
@@ -281,11 +289,11 @@ pub(crate) async fn sync_events_route(
changed: device_list_updates.into_iter().collect(),
left: device_list_left.into_iter().collect(),
},
device_one_time_keys_count: services()
device_one_time_keys_count: services
.users
.count_one_time_keys(&sender_user, &sender_device)?,
to_device: ToDevice {
events: services()
events: services
.users
.get_to_device_events(&sender_user, &sender_device)?,
},
@@ -311,16 +319,18 @@ pub(crate) async fn sync_events_route(
Ok(response)
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip_all, fields(user_id = %sender_user, room_id = %room_id), name = "left_room")]
async fn handle_left_room(
since: u64, room_id: &RoomId, sender_user: &UserId, left_rooms: &mut BTreeMap<ruma::OwnedRoomId, LeftRoom>,
next_batch_string: &str, full_state: bool, lazy_load_enabled: bool,
services: &Services, since: u64, room_id: &RoomId, sender_user: &UserId,
left_rooms: &mut BTreeMap<ruma::OwnedRoomId, LeftRoom>, next_batch_string: &str, full_state: bool,
lazy_load_enabled: bool,
) -> Result<()> {
// 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);
let left_count = services()
let left_count = services
.rooms
.state_cache
.get_left_count(room_id, sender_user)?;
@@ -330,11 +340,11 @@ async fn handle_left_room(
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
// Insert a leave event anyways
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(),
origin: None,
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()),
events: Vec::new(),
},
state: State {
state: RoomState {
events: vec![event.to_sync_state_event()],
},
},
@@ -377,27 +387,27 @@ async fn handle_left_room(
let mut left_state_events = Vec::new();
let since_shortstatehash = services()
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, since)?;
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(),
};
let Some(left_event_id) = services().rooms.state_accessor.room_state_get_id(
room_id,
&StateEventType::RoomMember,
sender_user.as_str(),
)?
let Some(left_event_id) =
services
.rooms
.state_accessor
.room_state_get_id(room_id, &StateEventType::RoomMember, sender_user.as_str())?
else {
error!("Left room but no left state event");
return Ok(());
};
let Some(left_shortstatehash) = services()
let Some(left_shortstatehash) = services
.rooms
.state_accessor
.pdu_shortstatehash(&left_event_id)?
@@ -406,13 +416,13 @@ async fn handle_left_room(
return Ok(());
};
let mut left_state_ids = services()
let mut left_state_ids = services
.rooms
.state_accessor
.state_full_ids(left_shortstatehash)
.await?;
let leave_shortstatekey = services()
let leave_shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&StateEventType::RoomMember, sender_user.as_str())?;
@@ -422,7 +432,7 @@ async fn handle_left_room(
let mut i: u8 = 0;
for (key, id) in left_state_ids {
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
|| 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
|| (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);
continue;
};
@@ -456,7 +466,7 @@ async fn handle_left_room(
prev_batch: Some(next_batch_string.to_owned()),
events: Vec::new(),
},
state: State {
state: RoomState {
events: left_state_events,
},
},
@@ -465,13 +475,11 @@ async fn handle_left_room(
}
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<()> {
use crate::service::presence::Presence;
// Take presence updates
for (user_id, _, presence_bytes) in services().presence.presence_since(since) {
if !services()
for (user_id, _, presence_bytes) in services.presence.presence_since(since) {
if !services
.rooms
.state_cache
.user_sees_user(syncing_user, &user_id)?
@@ -479,7 +487,9 @@ async fn process_presence_updates(
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) {
Entry::Vacant(slot) => {
slot.insert(presence_event);
@@ -513,19 +523,20 @@ async fn process_presence_updates(
#[allow(clippy::too_many_arguments)]
async fn load_joined_room(
sender_user: &UserId, sender_device: &DeviceId, room_id: &RoomId, since: u64, sincecount: PduCount,
next_batch: u64, next_batchcount: PduCount, lazy_load_enabled: bool, lazy_load_send_redundant: bool,
full_state: bool, device_list_updates: &mut HashSet<OwnedUserId>, left_encrypted_users: &mut HashSet<OwnedUserId>,
services: &Services, sender_user: &UserId, sender_device: &DeviceId, room_id: &RoomId, since: u64,
sincecount: PduCount, next_batch: u64, next_batchcount: PduCount, lazy_load_enabled: bool,
lazy_load_send_redundant: bool, full_state: bool, device_list_updates: &mut HashSet<OwnedUserId>,
left_encrypted_users: &mut HashSet<OwnedUserId>,
) -> Result<JoinedRoom> {
// Get and drop the lock to wait for remaining operations to finish
// 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);
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()
|| services()
|| services
.rooms
.user
.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());
}
services()
services
.rooms
.lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, room_id, sincecount)
@@ -544,11 +555,11 @@ async fn load_joined_room(
// 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")));
};
let since_shortstatehash = services()
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, since)?;
@@ -560,12 +571,12 @@ async fn load_joined_room(
} else {
// Calculates joined_member_count, invited_member_count and heroes
let calculate_counts = || {
let joined_member_count = services()
let joined_member_count = services
.rooms
.state_cache
.room_joined_count(room_id)?
.unwrap_or(0);
let invited_member_count = services()
let invited_member_count = services
.rooms
.state_cache
.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
// joined or invited until we have 5 or we reach the end
for hero in services()
for hero in services
.rooms
.timeline
.all_pdus(sender_user, room_id)?
@@ -594,8 +605,8 @@ async fn load_joined_room(
// The membership was and still is invite or join
if matches!(content.membership, MembershipState::Join | MembershipState::Invite)
&& (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_joined(&user_id, room_id)?
|| services.rooms.state_cache.is_invited(&user_id, room_id)?)
{
Ok::<_, Error>(Some(user_id))
} else {
@@ -622,7 +633,7 @@ async fn load_joined_room(
let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash
.and_then(|shortstatehash| {
services()
services
.rooms
.state_accessor
.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 current_state_ids = services()
let current_state_ids = services
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
@@ -654,13 +665,13 @@ async fn load_joined_room(
let mut i: u8 = 0;
for (shortstatekey, id) in current_state_ids {
let (event_type, state_key) = services()
let (event_type, state_key) = services
.rooms
.short
.get_statekey_from_short(shortstatekey)?;
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);
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
|| (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);
continue;
};
@@ -695,14 +706,14 @@ async fn load_joined_room(
}
// Reset lazy loading because this is an initial sync
services()
services
.rooms
.lazy_loading
.lazy_load_reset(sender_user, sender_device, room_id)?;
// The state_events above should contain all timeline_users, let's mark them as
// lazy loaded.
services()
services
.rooms
.lazy_loading
.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();
if since_shortstatehash != current_shortstatehash {
let current_state_ids = services()
let current_state_ids = services
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
.await?;
let since_state_ids = services()
let since_state_ids = services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
@@ -729,7 +740,7 @@ async fn load_joined_room(
for (key, id) in current_state_ids {
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);
continue;
};
@@ -740,13 +751,13 @@ async fn load_joined_room(
}
}
let encrypted_room = services()
let encrypted_room = services
.rooms
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")?
.is_some();
let since_encryption = services().rooms.state_accessor.state_get(
let since_encryption = services.rooms.state_accessor.state_get(
since_shortstatehash,
&StateEventType::RoomEncryption,
"",
@@ -781,7 +792,7 @@ async fn load_joined_room(
match new_membership {
MembershipState::Join => {
// 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);
}
},
@@ -798,7 +809,7 @@ async fn load_joined_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
device_list_updates.extend(
services()
services
.rooms
.state_cache
.room_members(room_id)
@@ -810,7 +821,7 @@ async fn load_joined_room(
.filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target
// 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;
}
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
room_id,
&event.sender,
)? || 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,
&StateEventType::RoomMember,
event.sender.as_str(),
@@ -866,7 +877,7 @@ async fn load_joined_room(
}
}
services()
services
.rooms
.lazy_loading
.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
device_list_updates.extend(
services()
services
.users
.keys_changed(room_id.as_ref(), since, None)
.filter_map(Result::ok),
@@ -892,7 +903,7 @@ async fn load_joined_room(
let notification_count = if send_notification_counts {
Some(
services()
services
.rooms
.user
.notification_count(sender_user, room_id)?
@@ -905,7 +916,7 @@ async fn load_joined_room(
let highlight_count = if send_notification_counts {
Some(
services()
services
.rooms
.user
.highlight_count(sender_user, room_id)?
@@ -933,7 +944,7 @@ async fn load_joined_room(
.map(|(_, pdu)| pdu.to_sync_room_event())
.collect();
let mut edus: Vec<_> = services()
let mut edus: Vec<_> = services
.rooms
.read_receipt
.readreceipts_since(room_id, since)
@@ -941,10 +952,10 @@ async fn load_joined_room(
.map(|(_, _, v)| v)
.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(
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"),
@@ -953,14 +964,14 @@ async fn load_joined_room(
// Save the state after this sync so we can send the correct state diff next
// sync
services()
services
.rooms
.user
.associate_token_shortstatehash(room_id, next_batch, current_shortstatehash)?;
Ok(JoinedRoom {
account_data: RoomAccountData {
events: services()
events: services
.account_data
.changes_since(Some(room_id), sender_user, since)?
.into_iter()
@@ -985,7 +996,7 @@ async fn load_joined_room(
prev_batch,
events: room_events,
},
state: State {
state: RoomState {
events: state_events
.iter()
.map(|pdu| pdu.to_sync_state_event())
@@ -999,16 +1010,16 @@ async fn load_joined_room(
}
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> {
let timeline_pdus;
let limited = if services()
let limited = if services
.rooms
.timeline
.last_timeline_count(sender_user, room_id)?
> roomsincecount
{
let mut non_timeline_pdus = services()
let mut non_timeline_pdus = services
.rooms
.timeline
.pdus_until(sender_user, room_id, PduCount::max())?
@@ -1040,8 +1051,10 @@ fn load_timeline(
Ok((timeline_pdus, limited))
}
fn share_encrypted_room(sender_user: &UserId, user_id: &UserId, ignore_room: &RoomId) -> Result<bool> {
Ok(services()
fn share_encrypted_room(
services: &Services, sender_user: &UserId, user_id: &UserId, ignore_room: &RoomId,
) -> Result<bool> {
Ok(services
.rooms
.user
.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_map(|other_room_id| {
Some(
services()
services
.rooms
.state_accessor
.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`)
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>> {
let sender_user = body.sender_user.expect("user is authenticated");
let sender_device = body.sender_device.expect("user is authenticated");
let mut body = body.body;
// 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
.pos
@@ -1082,21 +1095,19 @@ pub(crate) async fn sync_events_v4_route(
if globalsince == 0 {
if let Some(conn_id) = &body.conn_id {
services().users.forget_sync_request_connection(
sender_user.clone(),
sender_device.clone(),
conn_id.clone(),
);
services
.users
.forget_sync_request_connection(sender_user.clone(), sender_device.clone(), conn_id.clone());
}
}
// Get sticky parameters from cache
let known_rooms =
services()
services
.users
.update_sync_request_with_cache(sender_user.clone(), sender_device.clone(), &mut body);
let all_joined_rooms = services()
let all_joined_rooms = services
.rooms
.state_cache
.rooms_joined(&sender_user)
@@ -1104,7 +1115,7 @@ pub(crate) async fn sync_events_v4_route(
.collect::<Vec<_>>();
if body.extensions.to_device.enabled.unwrap_or(false) {
services()
services
.users
.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) {
// Look for device list updates of this account
device_list_changes.extend(
services()
services
.users
.keys_changed(sender_user.as_ref(), globalsince, None)
.filter_map(Result::ok),
);
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);
continue;
};
let since_shortstatehash = services()
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, globalsince)?;
let since_sender_member: Option<RoomMemberEventContent> = since_shortstatehash
.and_then(|shortstatehash| {
services()
services
.rooms
.state_accessor
.state_get(shortstatehash, &StateEventType::RoomMember, sender_user.as_str())
@@ -1148,7 +1159,7 @@ pub(crate) async fn sync_events_v4_route(
.ok()
});
let encrypted_room = services()
let encrypted_room = services
.rooms
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")?
@@ -1160,7 +1171,7 @@ pub(crate) async fn sync_events_v4_route(
continue;
}
let since_encryption = services().rooms.state_accessor.state_get(
let since_encryption = services.rooms.state_accessor.state_get(
since_shortstatehash,
&StateEventType::RoomEncryption,
"",
@@ -1171,12 +1182,12 @@ pub(crate) async fn sync_events_v4_route(
let new_encrypted_room = encrypted_room && since_encryption.is_none();
if encrypted_room {
let current_state_ids = services()
let current_state_ids = services
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
.await?;
let since_state_ids = services()
let since_state_ids = services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
@@ -1184,7 +1195,7 @@ pub(crate) async fn sync_events_v4_route(
for (key, id) in current_state_ids {
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);
continue;
};
@@ -1205,7 +1216,7 @@ pub(crate) async fn sync_events_v4_route(
match new_membership {
MembershipState::Join => {
// 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);
}
},
@@ -1222,7 +1233,7 @@ pub(crate) async fn sync_events_v4_route(
if joined_since_last_sync || new_encrypted_room {
// If the user is in a new encrypted room, give them all joined users
device_list_changes.extend(
services()
services
.rooms
.state_cache
.room_members(room_id)
@@ -1234,7 +1245,7 @@ pub(crate) async fn sync_events_v4_route(
.filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target
// 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
device_list_changes.extend(
services()
services
.users
.keys_changed(room_id.as_ref(), globalsince, None)
.filter_map(Result::ok),
);
}
for user_id in left_encrypted_users {
let dont_share_encrypted_room = services()
let dont_share_encrypted_room = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(Result::ok)
.filter_map(|other_room_id| {
Some(
services()
services
.rooms
.state_accessor
.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 {
services().users.update_sync_known_rooms(
services.users.update_sync_known_rooms(
sender_user.clone(),
sender_device.clone(),
conn_id.clone(),
@@ -1349,7 +1360,7 @@ pub(crate) async fn sync_events_v4_route(
let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions {
if !services().rooms.metadata.exists(room_id)? {
if !services.rooms.metadata.exists(room_id)? {
continue;
}
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 {
services().users.update_sync_known_rooms(
services.users.update_sync_known_rooms(
sender_user.clone(),
sender_device.clone(),
conn_id.clone(),
@@ -1386,7 +1397,7 @@ pub(crate) async fn sync_events_v4_route(
}
if let Some(conn_id) = &body.conn_id {
services().users.update_sync_subscriptions(
services.users.update_sync_subscriptions(
sender_user.clone(),
sender_device.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 {
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() {
continue;
@@ -1431,7 +1443,7 @@ pub(crate) async fn sync_events_v4_route(
let required_state = required_state_request
.iter()
.map(|state| {
services()
services
.rooms
.state_accessor
.room_state_get(room_id, &state.0, &state.1)
@@ -1442,7 +1454,7 @@ pub(crate) async fn sync_events_v4_route(
.collect();
// Heroes
let heroes = services()
let heroes = services
.rooms
.state_cache
.room_members(room_id)
@@ -1450,7 +1462,7 @@ pub(crate) async fn sync_events_v4_route(
.filter(|member| member != &sender_user)
.map(|member| {
Ok::<_, Error>(
services()
services
.rooms
.state_accessor
.get_member(room_id, &member)?
@@ -1491,11 +1503,11 @@ pub(crate) async fn sync_events_v4_route(
rooms.insert(
room_id.clone(),
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 {
ruma::JsOption::Some(heroes_avatar)
} 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::Null => ruma::JsOption::Null,
ruma::JsOption::Undefined => ruma::JsOption::Undefined,
@@ -1506,7 +1518,7 @@ pub(crate) async fn sync_events_v4_route(
invite_state: None,
unread_notifications: UnreadNotificationsCount {
highlight_count: Some(
services()
services
.rooms
.user
.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"),
),
notification_count: Some(
services()
services
.rooms
.user
.notification_count(&sender_user, room_id)?
@@ -1527,7 +1539,7 @@ pub(crate) async fn sync_events_v4_route(
prev_batch,
limited,
joined_count: Some(
services()
services
.rooms
.state_cache
.room_joined_count(room_id)?
@@ -1536,7 +1548,7 @@ pub(crate) async fn sync_events_v4_route(
.unwrap_or_else(|_| uint!(0)),
),
invited_count: Some(
services()
services
.rooms
.state_cache
.room_invited_count(room_id)?
@@ -1571,7 +1583,7 @@ pub(crate) async fn sync_events_v4_route(
extensions: sync_events::v4::Extensions {
to_device: if body.extensions.to_device.enabled.unwrap_or(false) {
Some(sync_events::v4::ToDevice {
events: services()
events: services
.users
.get_to_device_events(&sender_user, &sender_device)?,
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(),
left: device_list_left.into_iter().collect(),
},
device_one_time_keys_count: services()
device_one_time_keys_count: services
.users
.count_one_time_keys(&sender_user, &sender_device)?,
// Fallback keys are not yet supported
@@ -1592,7 +1604,7 @@ pub(crate) async fn sync_events_v4_route(
},
account_data: sync_events::v4::AccountData {
global: if body.extensions.account_data.enabled.unwrap_or(false) {
services()
services
.account_data
.changes_since(None, &sender_user, globalsince)?
.into_iter()
+16 -9
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use axum::extract::State;
use ruma::{
api::client::tag::{create_tag, delete_tag, get_tags},
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}`
///
/// Adds a tag to the room.
///
/// - 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 event = services()
let event = services
.account_data
.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
.insert(body.tag.clone().into(), body.tag_info.clone());
services().account_data.update(
services.account_data.update(
Some(&body.room_id),
sender_user,
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.
///
/// - 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 event = services()
let event = services
.account_data
.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());
services().account_data.update(
services.account_data.update(
Some(&body.room_id),
sender_user,
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.
///
/// - 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 event = services()
let event = services
.account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
+7 -4
View File
@@ -1,12 +1,15 @@
use axum::extract::State;
use ruma::{
api::client::{error::ErrorKind, threads::get_threads},
uint,
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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");
// 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
};
let threads = services()
let threads = services
.rooms
.threads
.threads_until(sender_user, &body.room_id, from, &body.include)?
.take(limit)
.filter_map(Result::ok)
.filter(|(_, pdu)| {
services()
services
.rooms
.state_accessor
.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 axum::extract::State;
use conduit::{Error, Result};
use ruma::{
api::{
client::{error::ErrorKind, to_device::send_event_to_device},
@@ -8,19 +10,19 @@ use ruma::{
to_device::DeviceIdOrAllDevices,
};
use crate::{services, user_is_local, Error, Result, Ruma};
use crate::Ruma;
/// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}`
///
/// Send a to-device event to a set of client devices.
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> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref();
// Check if this is a new transaction id
if services()
if services
.transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)?
.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_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();
map.insert(target_device_id_maybe.clone(), event.clone());
let mut messages = BTreeMap::new();
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(),
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent {
sender: sender_user.clone(),
@@ -53,7 +55,7 @@ pub(crate) async fn send_event_to_device_route(
match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => {
services().users.add_to_device_event(
services.users.add_to_device_event(
sender_user,
target_user_id,
target_device_id,
@@ -65,8 +67,8 @@ pub(crate) async fn send_event_to_device_route(
},
DeviceIdOrAllDevices::AllDevices => {
for target_device_id in services().users.all_device_ids(target_user_id) {
services().users.add_to_device_event(
for target_device_id in services.users.all_device_ids(target_user_id) {
services.users.add_to_device_event(
sender_user,
target_user_id,
&target_device_id?,
@@ -82,7 +84,7 @@ pub(crate) async fn send_event_to_device_route(
}
// Save transaction id with empty data
services()
services
.transaction_ids
.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 crate::{services, utils, Error, Result, Ruma};
use crate::{utils, Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}`
///
/// Sets the typing state of the sender user.
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> {
use create_typing_event::v3::Typing;
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services()
if !services
.rooms
.state_cache
.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 {
let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX),
services()
services
.globals
.config
.typing_client_timeout_min_s
.checked_mul(1000)
.unwrap(),
services()
services
.globals
.config
.typing_client_timeout_max_s
.checked_mul(1000)
.unwrap(),
);
services()
services
.rooms
.typing
.typing_add(
@@ -48,7 +49,7 @@ pub(crate) async fn create_typing_event_route(
)
.await?;
} else {
services()
services
.rooms
.typing
.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 conduit::warn;
use ruma::{
@@ -6,7 +7,7 @@ use ruma::{
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`
///
@@ -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)
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
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> {
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 {
joined: vec![],
next_batch_token: None,
});
}
let mutual_rooms: Vec<OwnedRoomId> = services()
let mutual_rooms: Vec<OwnedRoomId> = services
.rooms
.user
.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)
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>> {
get_room_summary(InsecureClientIp(client), body)
get_room_summary(State(services), InsecureClientIp(client), body)
.await
.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)
#[tracing::instrument(skip_all, fields(%client), name = "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> {
let sender_user = body.sender_user.as_ref();
let room_id = services()
.rooms
.alias
.resolve(&body.room_id_or_alias)
.await?;
let room_id = services.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"));
}
if sender_user.is_none()
&& !services()
&& !services
.rooms
.state_accessor
.is_world_readable(&room_id)
@@ -103,25 +103,25 @@ pub(crate) async fn get_room_summary(
Ok(get_summary::msc3266::Response {
room_id: room_id.clone(),
canonical_alias: services()
canonical_alias: services
.rooms
.state_accessor
.get_canonical_alias(&room_id)
.unwrap_or(None),
avatar_url: services()
avatar_url: services
.rooms
.state_accessor
.get_avatar(&room_id)?
.into_option()
.unwrap_or_default()
.url,
guest_can_join: services().rooms.state_accessor.guest_can_join(&room_id)?,
name: services()
guest_can_join: services.rooms.state_accessor.guest_can_join(&room_id)?,
name: services
.rooms
.state_accessor
.get_name(&room_id)
.unwrap_or(None),
num_joined_members: services()
num_joined_members: services
.rooms
.state_cache
.room_joined_count(&room_id)
@@ -132,21 +132,21 @@ pub(crate) async fn get_room_summary(
})
.try_into()
.expect("user count should not be that big"),
topic: services()
topic: services
.rooms
.state_accessor
.get_room_topic(&room_id)
.unwrap_or(None),
world_readable: services()
world_readable: services
.rooms
.state_accessor
.is_world_readable(&room_id)
.unwrap_or(false),
join_rule: services().rooms.state_accessor.get_join_rule(&room_id)?.0,
room_type: services().rooms.state_accessor.get_room_type(&room_id)?,
room_version: Some(services().rooms.state.get_room_version(&room_id)?),
join_rule: services.rooms.state_accessor.get_join_rule(&room_id)?.0,
room_type: services.rooms.state_accessor.get_room_type(&room_id)?,
room_version: Some(services.rooms.state.get_room_version(&room_id)?),
membership: if let Some(sender_user) = sender_user {
services()
services
.rooms
.state_accessor
.get_member(&room_id, sender_user)?
@@ -154,7 +154,7 @@ pub(crate) async fn get_room_summary(
} else {
None
},
encryption: services()
encryption: services
.rooms
.state_accessor
.get_room_encryption(&room_id)
+16 -14
View File
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use axum::{response::IntoResponse, Json};
use axum::{extract::State, response::IntoResponse, Json};
use ruma::api::client::{
discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
@@ -10,7 +10,7 @@ use ruma::api::client::{
error::ErrorKind,
};
use crate::{services, Error, Result, Ruma};
use crate::{Error, Result, Ruma};
/// # `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.
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> {
let client_url = match services().globals.well_known_client() {
let client_url = match services.globals.well_known_client() {
Some(url) => url.to_string(),
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`
///
/// 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> {
let support_page = services()
pub(crate) async fn well_known_support(
State(services): State<crate::State>, _body: Ruma<discover_support::Request>,
) -> Result<discover_support::Response> {
let support_page = services
.globals
.well_known_support_page()
.as_ref()
.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
if support_page.is_none() && role.is_none() {
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found."));
}
let email_address = services().globals.well_known_support_email().clone();
let matrix_id = services().globals.well_known_support_mxid().clone();
let email_address = services.globals.well_known_support_email().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 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
/// Web as a non-standard health check.
pub(crate) async fn syncv3_client_server_json() -> Result<impl IntoResponse> {
let server_url = match services().globals.well_known_client() {
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() {
Some(url) => url.to_string(),
None => match services().globals.well_known_server() {
None => match services.globals.well_known_server() {
Some(url) => url.to_string(),
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
/// homeserver. Endpoint is disabled if federation is disabled for privacy. This
/// only includes active users (not deactivated, no guests, etc)
pub(crate) async fn conduwuit_local_user_count() -> Result<impl IntoResponse> {
let user_count = services().users.list_local_users()?.len();
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();
Ok(Json(serde_json::json!({
"count": user_count
+11 -8
View File
@@ -1,3 +1,4 @@
use axum::extract::State;
use ruma::{
api::client::user_directory::search_users,
events::{
@@ -6,7 +7,7 @@ use ruma::{
},
};
use crate::{services, Result, Ruma};
use crate::{Result, Ruma};
/// # `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
/// 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 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...)
let user_id = user_id.ok()?;
let user = search_users::v3::User {
user_id: user_id.clone(),
display_name: services().users.displayname(&user_id).ok()?,
avatar_url: services().users.avatar_url(&user_id).ok()?,
display_name: services.users.displayname(&user_id).ok()?,
avatar_url: services.users.avatar_url(&user_id).ok()?,
};
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?
let mut user_visible = false;
let user_is_in_public_rooms = services()
let user_is_in_public_rooms = services
.rooms
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.any(|room| {
services()
services
.rooms
.state_accessor
.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 {
user_visible = true;
} else {
let user_is_in_shared_rooms = services()
let user_is_in_shared_rooms = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), user_id])
+16 -10
View File
@@ -1,12 +1,13 @@
use std::time::{Duration, SystemTime};
use axum::extract::State;
use base64::{engine::general_purpose, Engine as _};
use conduit::utils;
use conduit::{utils, Err};
use hmac::{Hmac, Mac};
use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch, UserId};
use sha1::Sha1;
use crate::{services, Result, Ruma};
use crate::{Result, Ruma};
const RANDOM_USER_ID_LENGTH: usize = 10;
@@ -16,14 +17,19 @@ type HmacSha1 = Hmac<Sha1>;
///
/// TODO: Returns information about the recommended turn server.
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> {
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 expiry = SecondsSinceUnixEpoch::from_system_time(
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("time is valid");
@@ -31,7 +37,7 @@ pub(crate) async fn turn_server_route(
let user = body.sender_user.unwrap_or_else(|| {
UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
&services().globals.config.server_name,
&services.globals.config.server_name,
)
.unwrap()
});
@@ -46,15 +52,15 @@ pub(crate) async fn turn_server_route(
(username, password)
} else {
(
services().globals.turn_username().clone(),
services().globals.turn_password().clone(),
services.globals.turn_username().clone(),
services.globals.turn_password().clone(),
)
};
Ok(get_turn_server_info::v3::Response {
username,
password,
uris: services().globals.turn_uris().to_vec(),
ttl: Duration::from_secs(services().globals.turn_ttl()),
uris: services.globals.turn_uris().to_vec(),
ttl: Duration::from_secs(services.globals.turn_ttl()),
})
}
+5 -4
View File
@@ -1,3 +1,5 @@
#![recursion_limit = "192"]
pub mod client;
pub mod router;
pub mod server;
@@ -5,11 +7,10 @@ pub mod server;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{debug_info, debug_warn, pdu::PduEvent, utils, Error, Result};
pub(crate) use service::{services, user_is_local};
pub(crate) use conduit::{debug_info, pdu::PduEvent, utils, Error, Result};
pub(crate) use service::services;
pub use crate::router::State;
pub(crate) use crate::router::{Ruma, RumaResponse};
pub(crate) use self::router::{Ruma, RumaResponse, State};
conduit::mod_ctor! {}
conduit::mod_dtor! {}
+2 -3
View File
@@ -3,6 +3,7 @@ mod auth;
mod handler;
mod request;
mod response;
pub mod state;
use axum::{
response::IntoResponse,
@@ -13,11 +14,9 @@ use conduit::{err, Server};
use http::Uri;
use self::handler::RouterExt;
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
pub(super) use self::{args::Args as Ruma, response::RumaResponse, state::State};
use crate::{client, server};
pub type State = &'static service::Services;
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config;
let router = router
+15 -11
View File
@@ -2,11 +2,12 @@ use std::{mem, ops::Deref};
use axum::{async_trait, body::Body, extract::FromRequest};
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 service::Services;
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
pub(crate) struct Args<T> {
@@ -35,18 +36,18 @@ pub(crate) struct Args<T> {
}
#[async_trait]
impl<T, S> FromRequest<S, Body> for Args<T>
impl<T> FromRequest<State, Body> for Args<T>
where
T: IncomingRequest,
{
type Rejection = Error;
async fn from_request(request: hyper::Request<Body>, _: &S) -> Result<Self, Self::Rejection> {
let mut request = request::from(request).await?;
async fn from_request(request: hyper::Request<Body>, services: &State) -> Result<Self, Self::Rejection> {
let mut request = request::from(services, request).await?;
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 {
body: make_body::<T>(&mut request, &mut json_body, &auth)?,
body: make_body::<T>(services, &mut request, &mut json_body, &auth)?,
origin: auth.origin,
sender_user: auth.sender_user,
sender_device: auth.sender_device,
@@ -62,13 +63,16 @@ impl<T> Deref for Args<T> {
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
T: IncomingRequest,
{
let body = if let Some(CanonicalJsonValue::Object(json_body)) = json_body {
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
@@ -77,9 +81,9 @@ where
.and_then(|auth| auth.get("session"))
.and_then(|session| session.as_str())
.and_then(|session| {
services().uiaa.get_uiaa_request(
services.uiaa.get_uiaa_request(
&user_id,
&auth.sender_device.clone().unwrap_or_else(|| "".into()),
&auth.sender_device.clone().unwrap_or_else(|| EMPTY.into()),
session,
)
});
+31 -19
View File
@@ -6,17 +6,17 @@ use axum_extra::{
typed_header::TypedHeaderRejectionReason,
TypedHeader,
};
use conduit::Err;
use conduit::{debug_info, warn, Err, Error, Result};
use http::uri::PathAndQuery;
use ruma::{
api::{client::error::ErrorKind, AuthScheme, Metadata},
server_util::authorization::XMatrix,
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
};
use tracing::warn;
use service::Services;
use super::request::Request;
use crate::{service::appservice::RegistrationInfo, services, Error, Result};
use crate::service::appservice::RegistrationInfo;
enum Token {
Appservice(Box<RegistrationInfo>),
@@ -33,7 +33,7 @@ pub(super) struct 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> {
let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?;
let token = match &bearer {
@@ -42,9 +42,9 @@ pub(super) async fn auth(
};
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))
} 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)))
} else {
Token::Invalid
@@ -57,7 +57,7 @@ pub(super) async fn auth(
match request.parts.uri.path() {
// TODO: can we check this better?
"/_matrix/client/v3/publicRooms" | "/_matrix/client/r0/publicRooms" => {
if !services()
if !services
.globals
.config
.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)) => {
Ok(Auth {
origin: None,
@@ -110,7 +110,7 @@ pub(super) async fn auth(
(AuthScheme::AccessToken, Token::None) => match request.parts.uri.path() {
// TODO: can we check this better?
"/_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 {
origin: None,
sender_user: None,
@@ -132,7 +132,7 @@ pub(super) async fn auth(
sender_device: Some(device_id),
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 {
sender_user: 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
.query
.user_id
@@ -159,7 +159,7 @@ fn auth_appservice(request: &Request, info: Box<RegistrationInfo>) -> Result<Aut
|| {
UserId::parse_with_server_name(
info.registration.sender_localpart.as_str(),
services().globals.server_name(),
services.globals.server_name(),
)
},
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."));
}
if !services().users.exists(&user_id)? {
if !services.users.exists(&user_id)? {
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> {
if !services().globals.allow_federation() {
async fn auth_server(
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."));
}
@@ -204,6 +206,17 @@ async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValu
})?;
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 =
BTreeMap::from_iter([(x_matrix.key.clone(), CanonicalJsonValue::String(x_matrix.sig.to_string()))]);
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 destination != &server_destination {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Invalid authorization."));
@@ -247,9 +260,8 @@ async fn auth_server(request: &mut Request, json_body: &Option<CanonicalJsonValu
request_map.insert("content".to_owned(), json_body.clone());
};
let keys_result = services()
.rooms
.event_handler
let keys_result = services
.server_keys
.fetch_signing_keys_for_server(origin, vec![x_matrix.key.to_string()])
.await;
+4 -5
View File
@@ -2,11 +2,10 @@ use std::str;
use axum::{extract::Path, RequestExt, RequestPartsExt};
use bytes::Bytes;
use conduit::err;
use conduit::{err, Result};
use http::request::Parts;
use serde::Deserialize;
use crate::{services, Result};
use service::Services;
#[derive(Deserialize)]
pub(super) struct QueryParams {
@@ -21,7 +20,7 @@ pub(super) struct Request {
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 (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 =
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)
.await
+79
View File
@@ -0,0 +1,79 @@
use std::{ops::Deref, sync::Arc};
use conduit_service::Services;
#[derive(Clone, Copy)]
pub struct State {
services: *const Services,
}
pub struct Guard {
services: Arc<Services>,
}
pub fn create(services: Arc<Services>) -> (State, Guard) {
let state = State {
services: Arc::into_raw(services.clone()),
};
let guard = Guard {
services,
};
(state, guard)
}
impl Drop for Guard {
fn drop(&mut self) {
let ptr = Arc::as_ptr(&self.services);
// SAFETY: Parity with Arc::into_raw() called in create(). This revivifies the
// Arc lost to State so it can be dropped, otherwise Services will leak.
let arc = unsafe { Arc::from_raw(ptr) };
debug_assert!(
Arc::strong_count(&arc) > 1,
"Services usually has more than one reference and is not dropped here"
);
}
}
impl Deref for State {
type Target = Services;
fn deref(&self) -> &Self::Target {
deref(&self.services).expect("dereferenced Services pointer in State must not be null")
}
}
/// SAFETY: State is a thin wrapper containing a raw const pointer to Services
/// in lieu of an Arc. Services is internally threadsafe. If State contains
/// additional fields this notice should be reevaluated.
unsafe impl Send for State {}
/// SAFETY: State is a thin wrapper containing a raw const pointer to Services
/// in lieu of an Arc. Services is internally threadsafe. If State contains
/// additional fields this notice should be reevaluated.
unsafe impl Sync for State {}
fn deref(services: &*const Services) -> Option<&Services> {
// SAFETY: We replaced Arc<Services> with *const Services in State. This is
// worth about 10 clones (20 reference count updates) for each request handled.
// Though this is not an incredibly large quantity, it's woefully unnecessary
// given the context as explained below; though it is not currently known to be
// a performance bottleneck, the front-line position justifies preempting it.
//
// Services is created prior to the axum/tower stack and Router, and prior
// to serving any requests through the handlers in this crate. It is then
// dropped only after all requests have completed, the listening sockets
// have been closed, axum/tower has been dropped. Thus Services is
// expected to live at least as long as any instance of State, making the
// constant updates to the prior Arc unnecessary to keep Services alive.
//
// Nevertheless if it is possible to accomplish this by annotating State
// with a lifetime to hold a reference (and be aware I have made a
// significant effort trying to make this work) this unsafety may not be
// necessary. It is either very difficult or impossible to get a
// lifetime'ed reference through Router / RumaHandler; though it is
// possible to pass a reference through axum's `with_state()` in trivial
// configurations as the only requirement of a State is Clone.
unsafe { services.as_ref() }
}

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