Compare commits

...
Author SHA1 Message Date
Timo Kösters 62f90ed04c improvement: efficient /sync, mutex for federation transactions 2021-07-01 19:55:26 +02:00
Timo Kösters 1a4cc81df1 fix: stuck messages
Conduit did not send the event in /sync because of a race condition.
There is a brief moment in time where Conduit accepted the event, but
did not store it yet. So when the client /syncs it updates the since
token without sending the event. I hope I fixed it by significantly
shortening the race-condition period.
2021-07-01 11:08:43 +02:00
Timo Kösters c07d744ece fix: e2ee verification 2021-06-30 20:31:51 +02:00
Devin Ragotzy 98f1480e2b Remove auth_cache using a closure to fetch events in state-res 2021-06-30 12:54:19 +02:00
Timo Kösters 05821d6fd5 improvement: pdu cache, /sync cache 2021-06-30 10:31:34 +02:00
Timo Kösters dcac1361ec improvement: /search works for multiple rooms 2021-06-21 15:21:00 +02:00
Timo Kösters fee7d3d2e3 Merge branch 'allow_appservice_registration' into 'master'
Always allow appservices to register new users

See merge request famedly/conduit!105
2021-06-21 10:15:48 +00:00
Timo Kösters 1a70ea5702 Merge branch 'upgrade-deps' into 'master'
Upgrade Ruma

See merge request famedly/conduit!104
2021-06-21 10:14:12 +00:00
phesch 637d9d3b6f Always allow appservices to register new users 2021-06-19 16:12:05 +02:00
Jonas Platte 808741bcb6 Remove unnecessary Option-wrapping and successive unwrapping 2021-06-17 20:44:29 +02:00
Jonas Platte b291e76572 Use try operator for Option more 2021-06-17 20:37:07 +02:00
Jonas Platte f3e630c064 Fix lots of clippy lints 2021-06-17 20:34:14 +02:00
Jonas Platte af2ce5803e Remove pointless double deserialization 2021-06-17 20:29:02 +02:00
Jonas Platte 7fa54e4411 Use Ruma-provided default power levels for shorter code 2021-06-17 20:14:37 +02:00
Jonas Platte f6046871f4 Upgrade Ruma 2021-06-17 20:12:36 +02:00
Timo Kösters 2ac7b6d411 Merge branch 'event_auth' into 'master'
feat: /event_auth

Closes #43

See merge request famedly/conduit!103
2021-06-14 09:44:07 +00:00
Timo Kösters 67f9592b17 feat: /event_auth 2021-06-14 11:36:40 +02:00
Timo Kösters 07271b7725 Merge branch 'roomdirfilter' into 'master'
improvement: filter our room directory

Closes #35

See merge request famedly/conduit!102
2021-06-14 09:33:20 +00:00
Timo Kösters 77a23f8969 improvement: filter our room directory
Fixes #35
2021-06-14 11:26:41 +02:00
Timo Kösters 01e01bf25e Merge branch 'state' into 'master'
feat: /state

Closes #47

See merge request famedly/conduit!101
2021-06-14 09:23:22 +00:00
Timo Kösters 3c9ea55938 feat: /state 2021-06-14 11:01:12 +02:00
Timo Kösters cf493f2b5b Merge branch 'userdir' into 'master'
User directory improvements

See merge request famedly/conduit!100
2021-06-12 16:52:49 +00:00
Timo Kösters e8f6708927 improvement: show more users in our user directory 2021-06-12 18:40:33 +02:00
Timo Kösters 8c6bcc47bf Merge branch 'rocksdb' into 'master'
Swappable database backend

See merge request famedly/conduit!98
2021-06-12 14:25:03 +00:00
Timo Kösters cd4bc520d8 improvement: feature flags for sled, rocksdb 2021-06-12 15:13:04 +02:00
hamidreza kalbasi 2078af59d8 fix fmt problems 2021-06-09 22:22:05 +02:00
hamidreza kalbasi 2385bd1cfd add migrations 2021-06-09 22:22:05 +02:00
hamidreza kalbasi affa124864 create media folder in init 2021-06-09 22:22:05 +02:00
hamidreza kalbasi ff841b73c5 use .keys() and remove unneccery .to_vec() 2021-06-09 22:22:04 +02:00
hamidreza kalbasi 804105479c fix fmt and clippy warnings 2021-06-09 22:22:04 +02:00
hamidreza kalbasi 972caacdc2 put media in filesystem 2021-06-09 22:22:04 +02:00
Timo Kösters d0ee823254 feat: swappable database backend 2021-06-09 22:21:52 +02:00
Timo Kösters 81715bd84d Merge branch 'account' into 'master'
Fix account and media bugs

See merge request famedly/conduit!97
2021-06-08 07:22:32 +00:00
Timo Kösters b3cec1d20d Merge branch 'master' into 'master'
Fix ruma dependency

Closes #95

See merge request famedly/conduit!96
2021-06-05 06:14:55 +00:00
Damian Poddebniak bb7a4220d3 Fix ruma dependency 2021-06-04 20:18:03 +02:00
Timo Kösters e1e529d818 fix: don't apply push rules for users of other homeservers 2021-05-30 22:01:12 +02:00
Timo Kösters 88cf043f94 fix: deactivate accounts that should be deactivated 2021-05-30 21:56:36 +02:00
Timo Kösters bff68e595b Merge branch 'account' into 'master'
fix: putting global account data works now

Closes #92

See merge request famedly/conduit!93
2021-05-29 09:14:47 +00:00
Timo Kösters 59dd3676d5 fix: putting global account data works now 2021-05-29 10:53:03 +02:00
Timo Kösters 1fa7ff2eb1 Merge branch 'claim' into 'master'
feat: implement /claim, handle to-device events

See merge request famedly/conduit!92
2021-05-28 21:36:46 +00:00
Timo Kösters deacdf6f58 fix: is_direct for locally invited users 2021-05-28 22:53:00 +02:00
Timo Kösters 5b5cc0574e feat: implement /claim, handle to-device events 2021-05-28 13:46:15 +02:00
Timo Kösters 953f2b005f Merge branch 'specify-and-document-msrv' into 'master'
Specify the minimum required Rust version in the manifest

See merge request famedly/conduit!91
2021-05-28 07:49:06 +00:00
Vadim Zeitlin f199b51f97 Move the link to cross-compiling guide to DEPLOY.md 2021-05-28 09:42:59 +02:00
Vadim Zeitlin 0f16a79888 Specify the minimum required Rust version in the manifest
Also mention it in the deployment guide.
2021-05-27 23:32:33 +02:00
Vadim Zeitlin 6af942814f Fix some typos in the README 2021-05-27 23:32:33 +02:00
Timo Kösters 0805f4ac2a Merge branch 'pdu-success' into 'master'
fix: also return successful PDUs in /send/:txnId

See merge request famedly/conduit!90
2021-05-27 21:24:43 +00:00
Gabriel Souza Franco 7db59c550f fix: also return successful PDUs in /send/:txnId 2021-05-27 18:10:48 -03:00
Timo Kösters 25b1cd2683 Merge branch 'stateres' into 'master'
fix: state resolution bugs

See merge request famedly/conduit!89
2021-05-27 09:10:53 +00:00
Timo Kösters daa1fc90a7 fix: state resolution bugs 2021-05-27 10:48:17 +02:00
Timo Kösters eeb0b59b76 Merge branch 'redaction_quotes' into 'master'
Fix redacted_because field being sent as a string

Closes #87

See merge request famedly/conduit!88
2021-05-26 15:58:00 +00:00
phesch ddcf1a715b Fix redacted_because field being sent as a string 2021-05-26 17:38:30 +02:00
Timo Kösters d1c0eaf628 Merge branch 'master' into 'master'
Fix docker-compose trusted_servers env var

See merge request famedly/conduit!87
2021-05-26 07:03:51 +00:00
Daniel Wiesenberg 8387ceacab Fix docker-compose trusted_servers env var 2021-05-25 21:34:31 +02:00
Timo Kösters 1aec80c57c Merge branch 'logs' into 'master'
fix: logs for ruma crate

See merge request famedly/conduit!86
2021-05-24 16:08:56 +00:00
Timo Kösters aacf6289db improvement: increase default max concurrent requests 2021-05-24 18:00:39 +02:00
Timo Kösters 57ecd81534 fix: logs for ruma crate 2021-05-24 13:22:30 +02:00
Timo Kösters ceaeab6816 Merge branch 'federation-errors' into 'master'
feat: forward federation errors to the client

See merge request famedly/conduit!85
2021-05-23 18:27:05 +00:00
Timo Kösters e5c7119516 feat: forward federation errors to the client 2021-05-23 16:46:46 +02:00
Timo Kösters 1939e62814 fmt 2021-05-23 11:11:20 +02:00
Timo Kösters 1ab209736a Merge branch 'forbidden' into 'master'
fix: Forbidden instead of InvalidParam when joining

See merge request famedly/conduit!84
2021-05-22 19:56:46 +00:00
Timo Kösters fd69ac621c fix: run ci with docker 2021-05-22 21:41:08 +02:00
Timo Kösters c1b2b468b8 fix: bad except in ruma wrapper 2021-05-22 21:33:31 +02:00
Timo Kösters 7e4dc65747 Merge branch 'master' into 'master'
Update Dockerfile and docker-compose

See merge request famedly/conduit!39
2021-05-22 19:26:03 +00:00
Daniel Wiesenberg 634fe04c33 Fix wrong env var name in traefik compose 2021-05-22 20:49:07 +02:00
Timo Kösters 90cd11d850 fix: Forbidden instead of InvalidParam when joining 2021-05-22 18:50:40 +02:00
Daniel Wiesenberg c6625d83e6 Add CONDUIT_TRUSTED_SERVERS config param 2021-05-22 16:53:48 +02:00
Daniel Wiesenberg 5a7ccbdfab Add CONDUIT_CONFIG to all relevant docker files
And mention that an empty string can be used to configure
Conduit purely with env vars.
2021-05-22 16:53:48 +02:00
Daniel Wiesenberg beb428d866 Update docker command in README
Mention the need for a config.
2021-05-22 16:53:48 +02:00
Daniel Wiesenberg 52a96b3d84 Update Dockerfile and docker-compose
- Dockerfile now tracks the gitlab repository and the master branch.
- docker-compose now points to conduit.toml instead of Rocket.toml and
its env vars were also renamed from ROCKET_ to CONDUIT_.
Furthermore vectorim/riot-web was changed to vectorim/element-web
2021-05-22 16:53:48 +02:00
Timo Kösters 42c9ba2e5c Merge branch 'appservice-docs' into 'master'
docs: APPSERVICES setup guide

See merge request famedly/conduit!83
2021-05-22 13:13:54 +00:00
Timo Kösters 47160e9e06 docs: APPSERVICES setup guide 2021-05-22 13:55:28 +02:00
Timo Kösters 488581cc51 Merge branch 'fix-sync' into 'master'
fix: too many syncs

See merge request famedly/conduit!82
2021-05-22 09:39:14 +00:00
Timo Kösters 9b77eb7bb7 fix: too many syncs 2021-05-22 11:31:15 +02:00
Timo Kösters acfd0aef27 Merge branch 'cargo-deb-packaging-update' into 'master'
Update cargo-deb packaging for recent changes

See merge request famedly/conduit!61
2021-05-22 09:03:43 +00:00
Timo Kösters 50348de1dd Merge branch 'improvements' into 'master'
improvement: federation get_keys and optimize signingkey storage

See merge request famedly/conduit!81
2021-05-22 08:49:18 +00:00
Timo Kösters 1b42770ab5 improvement: warning for small max_request_size values 2021-05-22 10:34:19 +02:00
Timo Kösters 3e2f742f30 fix: room version warnings and other bugs when joining rooms 2021-05-21 22:22:05 +02:00
Timo Kösters 989d843c40 fix: unauthorized pdus will be responded to with FORBIDDEN 2021-05-21 18:12:02 +02:00
Timo Kösters 09157b2096 improvement: federation get_keys and optimize signingkey storage
- get encryption keys over federation
- optimize signing key storage
- rate limit parsing of bad events
- rate limit signature fetching
- dependency bumps
2021-05-21 13:25:24 +02:00
Timo Kösters ae41bc5067 fix: run state res with old current state again
I'm a bit torn on the "auth check based on the current state of the
room". It can mean multiple things:

1. The state of the room before the homeserver looked at the event at
all. But that means if a message event from a user arrives, but we
didn't see their join event before, we soft fail the message (even
though we would find the join event when going through the auth events
of the event and doing state res)

2. The state of the room after doing state-res with the event and our
previous room state. We need to do this state resolution to find the new
room state anyway, so we could just use the new room state for the auth
check. The problem is that if the incoming event is a membership leave
event, the new room state does not allow another leave event. This is
obviously the wrong option.

3. The state of the room after doing state-res with the state **before**
the event and our previous room state. This will mean a lot more
calculations because we have to run state-res again

We used 2. before and now use 1. again
2021-05-17 10:58:44 +02:00
Timo Kösters 8f27e6123b feat: send read receipts over federation
currently they will only be sent if a PDU has to be sent as well
2021-05-17 10:25:27 +02:00
Timo Kösters 24793891e0 feat: implement GET /presence 2021-05-14 11:05:41 +02:00
Timo Kösters 767fe76d3e Merge branch 'jfowl-master-patch-93743' into 'master'
Try to improve CI build times by caching

See merge request famedly/conduit!79
2021-05-13 07:57:11 +00:00
Jonas Fowl 3ea0d2b567 Try to improve CI build times by caching 2021-05-13 07:57:11 +00:00
Timo Kösters f3e6311680 Merge branch 'clippy_ci' into 'master'
Add linting checks to CI

See merge request famedly/conduit!76
2021-05-12 18:44:41 +00:00
Timo Kösters 37b4a1cd0d Merge branch 'dbg_clippy' into 'master'
Add dbg_macro check

See merge request famedly/conduit!75
2021-05-12 18:44:01 +00:00
Timo Kösters 68c5c57486 Merge branch 'improvements' into 'master'
improvement: bug fixes and refactors

See merge request famedly/conduit!78
2021-05-12 18:29:26 +00:00
Timo Kösters f62258ba35 improvement: bug fixes and refactors
- power level content override adds to the default event instead of
replacing it
- sending code refactored to make edus possible
- remove presence events when restarting conduit
- remove room_id field from read receipts over /sync
- handle incoming read receipts
- fix array bounds bug in server_server.rs
2021-05-12 20:15:24 +02:00
Timo Kösters ad5e51dc25 Merge branch 'up-ruma' into 'master'
Upgrade Ruma

See merge request famedly/conduit!77
2021-05-08 07:46:26 +00:00
Jonas Platte af6fea3d4e Refactor some canonical JSON code 2021-05-08 02:13:01 +02:00
Jonas Platte a0457000ff Upgrade Ruma 2021-05-08 01:54:28 +02:00
Jonathan de Jong 80f1844476 fix rustup pls 2021-05-05 17:44:32 +00:00
Jonathan de Jong 9bf99e99a8 fix clippy rustup 2021-05-05 17:26:59 +00:00
Jonathan de Jong 442d0732a4 add rustup 2021-05-05 17:01:48 +00:00
Jonathan de Jong 9d2cc4d8b1 add cargo fmt check 2021-05-05 16:48:44 +00:00
Jonathan de Jong e48cd9b018 add clippy to CI 2021-05-05 16:41:22 +00:00
Jonathan de Jong 268ad3441c add dbg_macro check 2021-05-05 16:14:49 +00:00
Timo Kösters bd16850fd4 Merge branch 'trusted' into 'master'
fix: add trusted_servers to config and deploy guide

See merge request famedly/conduit!74
2021-05-05 10:42:55 +00:00
Timo Kösters 3408d74f93 fix: add trusted_servers to config and deploy guide 2021-05-05 12:27:49 +02:00
Timo Kösters 861cc76363 Merge branch 'uiaa' into 'master'
improvement: uiaa works like in synapse

See merge request famedly/conduit!73
2021-05-05 10:18:26 +00:00
Timo Kösters cf94b8e712 improvement: uiaa works like in synapse 2021-05-04 19:05:30 +02:00
Timo Kösters b42d34858e Merge branch 'up-ruma' into 'master'
Upgrade ruma and clean up request sending code

See merge request famedly/conduit!72
2021-05-02 16:59:31 +00:00
Jonas Platte e1c4e5c73e Return only event content in account_data endpoints, not the entire event 2021-04-29 21:28:01 +02:00
Jonas Platte 8bfaf09f37 Clean up reqwest::Response to http::Response conversion 2021-04-29 21:28:00 +02:00
Jonas Platte c28eba1e5b Bump ruma 2021-04-29 21:05:29 +02:00
Jonas Platte 08ca573df0 Remove explicit setting of content-type header
`try_into_http_request` takes care of this already.
2021-04-29 20:50:59 +02:00
Jonas Platte 61c522dcc0 Fix formatting 2021-04-29 20:50:59 +02:00
Jonas Platte b76af682a6 Fix clippy warnings 2021-04-29 20:50:59 +02:00
Paul van Tilburg f63a624743 Add text about using cargo-deb to the README 2021-04-23 19:11:42 +02:00
Paul van Tilburg 4fb2f17b83 Purge debconf changes from the DB on purge 2021-04-22 21:52:47 +02:00
Paul van Tilburg 19aaffeb23 Change the default port (follows DEPLOY.md) 2021-04-22 21:52:47 +02:00
Paul van Tilburg f3b1096417 Change the default library path (follows DEPLOY.md) 2021-04-22 21:52:47 +02:00
Paul van Tilburg 5bf43a4d7e Generate conduit.toml instead of debian environment file
Only generate this file once. I am not sure what to do with future
upgrades yet.
2021-04-22 21:52:40 +02:00
Paul van Tilburg ab58609d3d No longer use/support a local environment file 2021-04-16 22:07:58 +02:00
67 changed files with 4724 additions and 2717 deletions
+15
View File
@@ -1,13 +1,28 @@
image: "rust:latest" image: "rust:latest"
default:
tags: [docker]
cache:
paths:
- target
- cargohome
variables: variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
CARGO_HOME: "cargohome"
FF_USE_FASTZIP: 1
before_script: before_script:
- mkdir -p $CARGO_HOME && echo "using $CARGO_HOME to cache cargo deps"
- apt-get update -yqq - apt-get update -yqq
- apt-get install -yqq --no-install-recommends build-essential libssl-dev pkg-config - apt-get install -yqq --no-install-recommends build-essential libssl-dev pkg-config
- rustup component add clippy rustfmt
test:cargo: test:cargo:
script: script:
- rustc --version && cargo --version # Print version info for debugging - rustc --version && cargo --version # Print version info for debugging
- cargo test --workspace --verbose --locked - cargo test --workspace --verbose --locked
- cargo fmt --all -- --check
- cargo clippy
+48
View File
@@ -0,0 +1,48 @@
# Setting up Appservices
## Getting help
If you run into any problems while setting up an Appservice, write an email to `timo@koesters.xyz`, ask us in `#conduit:matrix.org` or [open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new).
## Tested appservices
Here are some appservices we tested and that work with Conduit:
- matrix-appservice-discord
- mautrix-hangouts
- mautrix-telegram
## Set up the appservice
Follow whatever instructions are given by the appservice. This usually includes
downloading, changing its config (setting domain, homeserver url, port etc.)
and later starting it.
At some point the appservice guide should ask you to add a registration yaml
file to the homeserver. In Synapse you would do this by adding the path to the
homeserver.yaml, but in Conduit you can do this from within Matrix:
First, go into the #admins room of your homeserver. The first person that
registered on the homeserver automatically joins it. Then send a message into
the room like this:
@conduit:your.server.name: register_appservice
```
paste
the
contents
of
the
yaml
registration
here
```
You can confirm it worked by sending a message like this:
`@conduit:your.server.name: list_appservices`
The @conduit bot should answer with `Appservices (1): your-bridge`
Then you are done. Conduit will send messages to the appservices and the
appservice can send requests to the homeserver. You don't need to restart
Conduit, but if it doesn't work, restarting while the appservice is running
could help.
Generated
+421 -73
View File
@@ -103,6 +103,25 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bindgen"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@@ -120,6 +139,15 @@ dependencies = [
"constant_time_eq", "constant_time_eq",
] ]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.6.1" version = "3.6.1"
@@ -153,6 +181,15 @@ dependencies = [
"jobserver", "jobserver",
] ]
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@@ -178,6 +215,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clang-sys"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]] [[package]]
name = "color_quant" name = "color_quant"
version = "1.1.0" version = "1.1.0"
@@ -195,14 +243,16 @@ dependencies = [
"image", "image",
"jsonwebtoken", "jsonwebtoken",
"log", "log",
"lru-cache",
"opentelemetry", "opentelemetry",
"opentelemetry-jaeger", "opentelemetry-jaeger",
"pretty_env_logger", "pretty_env_logger",
"rand", "rand 0.8.3",
"regex", "regex",
"reqwest", "reqwest",
"ring", "ring",
"rocket", "rocket",
"rocksdb",
"ruma", "ruma",
"rust-argon2", "rust-argon2",
"rustls", "rustls",
@@ -211,7 +261,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"sled", "sled",
"state-res",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@@ -221,6 +270,12 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "const-oid"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22"
[[package]] [[package]]
name = "const_fn" name = "const_fn"
version = "0.4.7" version = "0.4.7"
@@ -266,6 +321,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.2.1" version = "1.2.1"
@@ -299,6 +363,19 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "curve25519-dalek"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3"
dependencies = [
"byteorder",
"digest",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.3.2" version = "2.3.2"
@@ -315,6 +392,15 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "der"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f215f706081a44cb702c71c39a52c05da637822e9c1645a50b7202689e982d"
dependencies = [
"const-oid",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.13" version = "0.99.13"
@@ -357,6 +443,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "directories" name = "directories"
version = "3.0.1" version = "3.0.1"
@@ -389,6 +484,29 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "ed25519"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d0860415b12243916284c67a9be413e044ee6668247b99ba26d94b2bc06c8f6"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand 0.7.3",
"serde",
"sha2",
"zeroize",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@@ -583,6 +701,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@@ -796,6 +924,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "indoc"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136"
dependencies = [
"unindent",
]
[[package]] [[package]]
name = "inlinable_string" name = "inlinable_string"
version = "0.1.14" version = "0.1.14"
@@ -912,12 +1049,40 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.93" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "libloading"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "librocksdb-sys"
version = "6.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da125e1c0f22c7cae785982115523a0738728498547f415c9054cb17c7e89f9"
dependencies = [
"bindgen",
"cc",
"glob",
"libc",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.4" version = "0.5.4"
@@ -1062,6 +1227,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.6"
@@ -1139,6 +1314,12 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.2" version = "0.1.2"
@@ -1157,7 +1338,7 @@ dependencies = [
"lazy_static", "lazy_static",
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
"rand", "rand 0.8.3",
"thiserror", "thiserror",
] ]
@@ -1237,6 +1418,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "pem" name = "pem"
version = "0.8.3" version = "0.8.3"
@@ -1286,6 +1473,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d156817ae0125e8aa5067710b0db24f0984830614f99875a70aa5e3b74db69"
dependencies = [
"der",
"spki",
"zeroize",
]
[[package]] [[package]]
name = "png" name = "png"
version = "0.16.8" version = "0.16.8"
@@ -1373,6 +1571,19 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.3" version = "0.8.3"
@@ -1380,9 +1591,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.0",
"rand_core", "rand_core 0.6.2",
"rand_hc", "rand_hc 0.3.0",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
] ]
[[package]] [[package]]
@@ -1392,7 +1613,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.2",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
] ]
[[package]] [[package]]
@@ -1404,13 +1634,22 @@ dependencies = [
"getrandom 0.2.2", "getrandom 0.2.2",
] ]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.6.2",
] ]
[[package]] [[package]]
@@ -1575,7 +1814,7 @@ dependencies = [
"num_cpus", "num_cpus",
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"rand", "rand 0.8.3",
"ref-cast", "ref-cast",
"rocket_codegen", "rocket_codegen",
"rocket_http", "rocket_http",
@@ -1631,10 +1870,20 @@ dependencies = [
"uncased", "uncased",
] ]
[[package]]
name = "rocksdb"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c749134fda8bfc90d0de643d59bfc841dcb3ac8a1062e12b6754bd60235c48b3"
dependencies = [
"libc",
"librocksdb-sys",
]
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.0.3" version = "0.2.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@@ -1649,12 +1898,13 @@ dependencies = [
"ruma-push-gateway-api", "ruma-push-gateway-api",
"ruma-serde", "ruma-serde",
"ruma-signatures", "ruma-signatures",
"ruma-state-res",
] ]
[[package]] [[package]]
name = "ruma-api" name = "ruma-api"
version = "0.17.0-alpha.4" version = "0.17.1"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -1669,8 +1919,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-api-macros" name = "ruma-api-macros"
version = "0.17.0-alpha.4" version = "0.17.1"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@@ -1680,8 +1930,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.2.0-alpha.3" version = "0.3.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"ruma-api", "ruma-api",
"ruma-common", "ruma-common",
@@ -1694,8 +1944,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.10.0-alpha.3" version = "0.11.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"assign", "assign",
"bytes", "bytes",
@@ -1714,12 +1964,11 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.5.0" version = "0.5.4"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"js_int", "js_int",
"maplit",
"ruma-identifiers", "ruma-identifiers",
"ruma-serde", "ruma-serde",
"serde", "serde",
@@ -1730,9 +1979,10 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.22.0-alpha.3" version = "0.23.2"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"indoc",
"js_int", "js_int",
"ruma-common", "ruma-common",
"ruma-events-macros", "ruma-events-macros",
@@ -1744,8 +1994,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events-macros" name = "ruma-events-macros"
version = "0.22.0-alpha.3" version = "0.23.2"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@@ -1755,8 +2005,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.1.0-alpha.2" version = "0.2.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-api", "ruma-api",
@@ -1770,11 +2020,11 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers" name = "ruma-identifiers"
version = "0.19.0" version = "0.19.4"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"paste", "paste",
"rand", "rand 0.8.3",
"ruma-identifiers-macros", "ruma-identifiers-macros",
"ruma-identifiers-validation", "ruma-identifiers-validation",
"ruma-serde", "ruma-serde",
@@ -1784,10 +2034,9 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-macros" name = "ruma-identifiers-macros"
version = "0.19.0" version = "0.19.4"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"proc-macro2",
"quote", "quote",
"ruma-identifiers-validation", "ruma-identifiers-validation",
"syn", "syn",
@@ -1795,26 +2044,26 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.3.0" version = "0.4.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.1.0-alpha.1" version = "0.2.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"js_int",
"ruma-api", "ruma-api",
"ruma-common", "ruma-common",
"ruma-identifiers", "ruma-identifiers",
"ruma-serde", "ruma-serde",
"serde", "serde",
"serde_json",
] ]
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.1.0-alpha.1" version = "0.2.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-api", "ruma-api",
@@ -1828,8 +2077,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-serde" name = "ruma-serde"
version = "0.3.1" version = "0.4.1"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"bytes", "bytes",
"form_urlencoded", "form_urlencoded",
@@ -1842,8 +2091,8 @@ dependencies = [
[[package]] [[package]]
name = "ruma-serde-macros" name = "ruma-serde-macros"
version = "0.3.1" version = "0.4.1"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@@ -1853,15 +2102,37 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.7.0" version = "0.8.0"
source = "git+https://github.com/ruma/ruma?rev=d27584ae3bdc035529e7389f1c392d4c96f9f8eb#d27584ae3bdc035529e7389f1c392d4c96f9f8eb" source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [ dependencies = [
"base64 0.13.0", "base64 0.13.0",
"ring", "ed25519-dalek",
"pkcs8",
"rand 0.7.3",
"ruma-identifiers", "ruma-identifiers",
"ruma-serde", "ruma-serde",
"serde_json", "serde_json",
"untrusted", "sha2",
"thiserror",
"tracing",
]
[[package]]
name = "ruma-state-res"
version = "0.2.0"
source = "git+https://github.com/DevinR528/ruma?branch=state-closure#95208b9d03876e7c85543fe8655ceb2f7dc76363"
dependencies = [
"itertools 0.10.0",
"js_int",
"maplit",
"ruma-common",
"ruma-events",
"ruma-identifiers",
"ruma-serde",
"serde",
"serde_json",
"thiserror",
"tracing",
] ]
[[package]] [[package]]
@@ -1876,6 +2147,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@@ -1994,18 +2271,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.125" version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.125" version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2053,6 +2330,19 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"opaque-debug",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.1" version = "0.1.1"
@@ -2062,6 +2352,12 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.3.0" version = "1.3.0"
@@ -2071,6 +2367,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "signature"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68"
[[package]] [[package]]
name = "simple_asn1" name = "simple_asn1"
version = "0.4.1" version = "0.4.1"
@@ -2138,6 +2440,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spki"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521"
dependencies = [
"der",
]
[[package]] [[package]]
name = "stable-pattern" name = "stable-pattern"
version = "0.1.0" version = "0.1.0"
@@ -2164,20 +2475,6 @@ dependencies = [
"loom", "loom",
] ]
[[package]]
name = "state-res"
version = "0.1.0"
source = "git+https://github.com/ruma/state-res?rev=ce665d213fffeaa47e146d01c6b87f9eb9feaa52#ce665d213fffeaa47e146d01c6b87f9eb9feaa52"
dependencies = [
"itertools 0.10.0",
"log",
"maplit",
"ruma",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "stdweb" name = "stdweb"
version = "0.4.20" version = "0.4.20"
@@ -2227,6 +2524,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "subtle"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.70" version = "1.0.70"
@@ -2238,6 +2541,18 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "synstructure"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.2.0" version = "3.2.0"
@@ -2246,7 +2561,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"rand", "rand 0.8.3",
"redox_syscall 0.2.6", "redox_syscall 0.2.6",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi",
@@ -2447,9 +2762,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.25" version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"pin-project-lite", "pin-project-lite",
@@ -2470,9 +2785,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.17" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
] ]
@@ -2550,7 +2865,7 @@ dependencies = [
"ipnet", "ipnet",
"lazy_static", "lazy_static",
"log", "log",
"rand", "rand 0.8.3",
"smallvec", "smallvec",
"thiserror", "thiserror",
"tinyvec", "tinyvec",
@@ -2594,6 +2909,12 @@ dependencies = [
"unchecked-index", "unchecked-index",
] ]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]] [[package]]
name = "ubyte" name = "ubyte"
version = "0.10.1" version = "0.10.1"
@@ -2649,6 +2970,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "unindent"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"
@@ -2865,6 +3192,27 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.5.4+zstd.1.4.7" version = "0.5.4+zstd.1.4.7"
+14 -12
View File
@@ -15,21 +15,17 @@ edition = "2018"
# Used to handle requests # Used to handle requests
# TODO: This can become optional as soon as proper configs are supported # TODO: This can become optional as soon as proper configs are supported
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "801e04bd5369eb39e126c75f6d11e1e9597304d8", features = ["tls"] } # Used to handle requests rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "801e04bd5369eb39e126c75f6d11e1e9597304d8", features = ["tls"] } # Used to handle requests
#rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] }
# Used for matrix spec type definitions and helpers # Used for matrix spec type definitions and helpers
ruma = { git = "https://github.com/ruma/ruma", rev = "d27584ae3bdc035529e7389f1c392d4c96f9f8eb", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] } #ruma = { git = "https://github.com/ruma/ruma", rev = "5a7e2cddcf257e367465cced51442c91e8f557c9", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "220d5b4a76b3b781f7f8297fbe6b14473b04214b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] } ruma = { git = "https://github.com/DevinR528/ruma", branch = "state-closure", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
#ruma = { path = "../ruma/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] } #ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
# Used when doing state resolution
state-res = { git = "https://github.com/ruma/state-res", rev = "ce665d213fffeaa47e146d01c6b87f9eb9feaa52", features = ["unstable-pre-spec"] }
#state-res = { path = "../state-res", features = ["unstable-pre-spec"] }
# Used for long polling and federation sender, should be the same as rocket::tokio # Used for long polling and federation sender, should be the same as rocket::tokio
tokio = "1.2.0" tokio = "1.2.0"
# Used for storing data permanently # Used for storing data permanently
sled = { version = "0.34.6", features = ["compression", "no_metrics"] } sled = { version = "0.34.6", features = ["compression", "no_metrics"], optional = true }
rocksdb = { version = "0.16.0", features = ["multi-threaded-cf"], optional = true }
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] } #sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
# Used for the http request / response body type for Ruma endpoints used with reqwest # Used for the http request / response body type for Ruma endpoints used with reqwest
@@ -77,9 +73,12 @@ tracing-subscriber = "0.2.16"
tracing-opentelemetry = "0.11.0" tracing-opentelemetry = "0.11.0"
opentelemetry-jaeger = "0.11.0" opentelemetry-jaeger = "0.11.0"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
lru-cache = "0.1.2"
[features] [features]
default = ["conduit_bin"] default = ["conduit_bin", "backend_sled"]
backend_sled = ["sled"]
backend_rocksdb = ["rocksdb"]
conduit_bin = [] # TODO: add rocket to this when it is optional conduit_bin = [] # TODO: add rocket to this when it is optional
[[bin]] [[bin]]
@@ -103,13 +102,16 @@ instead of a server that has high scalability."""
section = "net" section = "net"
priority = "optional" priority = "optional"
assets = [ assets = [
["debian/env.local", "etc/matrix-conduit/local", "644"],
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"], ["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
["README.md", "usr/share/doc/matrix-conduit/", "644"], ["README.md", "usr/share/doc/matrix-conduit/", "644"],
["target/release/conduit", "usr/sbin/matrix-conduit", "755"], ["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
] ]
conf-files = [ conf-files = [
"/etc/matrix-conduit/local" "/etc/matrix-conduit/conduit.toml"
] ]
maintainer-scripts = "debian/" maintainer-scripts = "debian/"
systemd-units = { unit-name = "matrix-conduit" } systemd-units = { unit-name = "matrix-conduit" }
# For flamegraphs:
#[profile.release]
#debug = true
+14 -2
View File
@@ -6,7 +6,7 @@ If you run into any problems while setting up Conduit, write an email to `timo@k
## Installing Conduit ## Installing Conduit
You have to download the binary that fits your machine. Run `uname -m` to see You may simply download the binary that fits your machine. Run `uname -m` to see
what you need. Now copy the right url: what you need. Now copy the right url:
- x84_64: `https://conduit.rs/master/x86_64/conduit-bin` - x84_64: `https://conduit.rs/master/x86_64/conduit-bin`
- armv7: `https://conduit.rs/master/armv7/conduit-bin` - armv7: `https://conduit.rs/master/armv7/conduit-bin`
@@ -18,6 +18,14 @@ $ sudo wget -O /usr/local/bin/matrix-conduit <url>
$ sudo chmod +x /usr/local/bin/matrix-conduit $ sudo chmod +x /usr/local/bin/matrix-conduit
``` ```
Alternatively, you may compile the binary yourself using
```bash
$ cargo build --release
```
Note that this currently requires Rust 1.50.
If you want to cross compile Conduit to another architecture, read the [Cross-Compile Guide](CROSS_COMPILE.md).
## Adding a Conduit user ## Adding a Conduit user
While Conduit can run as any user it is usually better to use dedicated users for different services. While Conduit can run as any user it is usually better to use dedicated users for different services.
@@ -96,8 +104,10 @@ allow_registration = false
allow_encryption = true allow_encryption = true
allow_federation = true allow_federation = true
trusted_servers = ["matrix.org"]
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024 #cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time #max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
#workers = 4 # default: cpu core count * 2 #workers = 4 # default: cpu core count * 2
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
@@ -207,3 +217,5 @@ Set it to start automatically when your system boots with:
```bash ```bash
$ sudo systemctl enable conduit $ sudo systemctl enable conduit
``` ```
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
+5 -3
View File
@@ -30,7 +30,7 @@ COPY . .
RUN if [[ $LOCAL == "true" ]]; then \ RUN if [[ $LOCAL == "true" ]]; then \
cargo install --path . ; \ cargo install --path . ; \
else \ else \
cargo install --git "https://github.com/timokoesters/conduit.git" --rev ${GIT_REF}; \ cargo install --git "https://gitlab.com/famedly/conduit.git" --rev ${GIT_REF}; \
fi fi
########################## RUNTIME IMAGE ########################## ########################## RUNTIME IMAGE ##########################
@@ -40,7 +40,9 @@ FROM alpine:3.12
ARG CREATED ARG CREATED
ARG VERSION ARG VERSION
ARG GIT_REF=HEAD ARG GIT_REF=origin/master
ENV CONDUIT_CONFIG="/srv/conduit/conduit.toml"
# Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md # Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md
# including a custom label specifying the build command # including a custom label specifying the build command
@@ -52,7 +54,7 @@ LABEL org.opencontainers.image.created=${CREATED} \
org.opencontainers.image.description="A Matrix homeserver written in Rust" \ org.opencontainers.image.description="A Matrix homeserver written in Rust" \
org.opencontainers.image.url="https://conduit.rs/" \ org.opencontainers.image.url="https://conduit.rs/" \
org.opencontainers.image.revision=${GIT_REF} \ org.opencontainers.image.revision=${GIT_REF} \
org.opencontainers.image.source="https://git.koesters.xyz/timo/conduit.git" \ org.opencontainers.image.source="https://gitlab.com/famedly/conduit.git" \
org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.documentation="" \ org.opencontainers.image.documentation="" \
org.opencontainers.image.ref.name="" \ org.opencontainers.image.ref.name="" \
+27 -2
View File
@@ -14,7 +14,7 @@ Yes! Just open a Matrix client (<https://app.element.io> or Element Android for
example) and register on the `https://conduit.koesters.xyz` homeserver. example) and register on the `https://conduit.koesters.xyz` homeserver.
#### What is it build on? #### What is it built on?
- [Ruma](https://www.ruma.io): Useful structures for endpoint requests and - [Ruma](https://www.ruma.io): Useful structures for endpoint requests and
responses that can be (de)serialized responses that can be (de)serialized
@@ -52,15 +52,40 @@ Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit
Download or compile a Conduit binary, set up the config and call it from somewhere like a systemd script. [Read Download or compile a Conduit binary, set up the config and call it from somewhere like a systemd script. [Read
more](DEPLOY.md) more](DEPLOY.md)
If you want to connect an Appservice to Conduit, take a look at the [Appservice Guide](APPSERVICES.md).
##### Deploy using a Debian package
You need to have the `deb` helper command installed that creates Debian packages from Cargo projects (see [cargo-deb](https://github.com/mmstick/cargo-deb/) for more info):
```shell
$ cargo install cargo-deb
```
Then, you can create and install a Debian package at a whim:
```shell
$ cargo deb
$ dpkg -i target/debian/matrix-conduit_0.1.0_amd64.deb
```
This will build, package, install, configure and start Conduit. [Read more](debian/README.Debian).
Note that `cargo deb` supports [cross-compilation](https://github.com/mmstick/cargo-deb/#cross-compilation) too!
Official Debian packages will follow once Conduit starts to have stable releases.
##### Deploy using Docker ##### Deploy using Docker
Pull and run the docker image with Pull and run the docker image with
``` bash ``` bash
docker pull matrixconduit/matrix-conduit:latest docker pull matrixconduit/matrix-conduit:latest
docker run -d -p 8448:8000 -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest docker run -d -p 8448:8000 -v ~/conduit.toml:/srv/conduit/conduit.toml -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
``` ```
> <b>Note:</b> You also need to supply a `conduit.toml` config file, you can find an example [here](./conduit-example.toml).
> Or you can pass in `-e CONDUIT_CONFIG=""` and configure Conduit purely with env vars.
Or build and run it with docker or docker-compose. [Read more](docker/README.md) Or build and run it with docker or docker-compose. [Read more](docker/README.md)
+4 -2
View File
@@ -11,7 +11,7 @@
# YOU NEED TO EDIT THIS # YOU NEED TO EDIT THIS
#server_name = "your.server.name" #server_name = "your.server.name"
# This is the only directly where Conduit will save its data # This is the only directory where Conduit will save its data
database_path = "/var/lib/conduit/conduit.db" database_path = "/var/lib/conduit/conduit.db"
# The port Conduit will be running on. You need to set up a reverse proxy in # The port Conduit will be running on. You need to set up a reverse proxy in
@@ -33,8 +33,10 @@ max_request_size = 20_000_000 # in bytes
# Enable jaeger to support monitoring and troubleshooting through jaeger # Enable jaeger to support monitoring and troubleshooting through jaeger
#allow_jaeger = false #allow_jaeger = false
trusted_servers = ["matrix.org"]
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024 #cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time #max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
#log = "info,state_res=warn,rocket=off,_=off,sled=off" #log = "info,state_res=warn,rocket=off,_=off,sled=off"
#workers = 4 # default: cpu core count * 2 #workers = 4 # default: cpu core count * 2
+10 -10
View File
@@ -4,25 +4,25 @@ Conduit for Debian
Configuration Configuration
------------- -------------
When installed, Debconf handles the configuration of the homeserver (host)name, When installed, Debconf generates the configuration of the homeserver
the address and port it listens on. These configuration variables end up in (host)name, the address and port it listens on. This configuration ends up in
/etc/matrix-conduit/debian. /etc/matrix-conduit/conduit.toml.
You can tweak more detailed settings by uncommenting and setting the variables You can tweak more detailed settings by uncommenting and setting the variables
in /etc/matrix-conduit/local. This involves settings such as the maximum file in /etc/matrix-conduit/conduit.toml. This involves settings such as the maximum
size for download/upload, enabling federation, etc. file size for download/upload, enabling federation, etc.
Running Running
------- -------
The package uses the matrix-conduit.service systemd unit file to start and The package uses the matrix-conduit.service systemd unit file to start and
stop Conduit. It loads the configuration files mentioned above to set up the stop Conduit. It loads the configuration file mentioned above to set up the
environment before running the server. environment before running the server.
This package assumes by default that Conduit is placed behind a reverse proxy This package assumes by default that Conduit will be placed behind a reverse
such as Apache or nginx. This default deployment entails just listening on proxy such as Apache or nginx. This default deployment entails just listening
127.0.0.1 and the free port 14004 and is reachable via a client using the URL on 127.0.0.1 and the free port 6167 and is reachable via a client using the URL
http://localhost:14004. http://localhost:6167.
At a later stage this packaging may support also setting up TLS and running At a later stage this packaging may support also setting up TLS and running
stand-alone. In this case, however, you need to set up some certificates and stand-alone. In this case, however, you need to set up some certificates and
-33
View File
@@ -1,33 +0,0 @@
# Conduit homeserver local configuration
#
# Conduit is an application based on the Rocket web framework.
# Configuration of Conduit happens via Debconf (see the resulting config in
# `/etc/matrix-conduit/debian`) and optionally by uncommenting and tweaking the
# variables in this file below.
# The maximum size of a Matrix HTTP requests in bytes.
#
# This mostly affects the size of files that can be downloaded/uploaded.
# It defaults to 20971520 (20MB).
#ROCKET_MAX_REQUEST_SIZE=20971520
# Whether user registration is allowed.
#
# User registration is not disabled by default.
#ROCKET_REGISTRATION_DISABLED=false
# Whether encryption is enabled.
#
# (End-to-end) encryption is not disabled by default.
#ROCKET_ENCRYPTION_DISABLED=false
# Whether federation with other Matrix servers is enabled.
#
# Federation is not enabled by default; it is still experimental.
#ROCKET_FEDERATION_ENABLED=false
# The log level of the homeserver.
#
# The log level is "critical" by default.
# Allowed values are: "off", "normal", "debug", "critical"
#ROCKET_LOG="critical"
+1 -4
View File
@@ -34,10 +34,7 @@ SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM SystemCallErrorNumber=EPERM
StateDirectory=matrix-conduit StateDirectory=matrix-conduit
Environment="ROCKET_ENV=production" Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
Environment="ROCKET_DATABASE_PATH=/var/lib/matrix-conduit"
EnvironmentFile=/etc/matrix-conduit/debian
EnvironmentFile=/etc/matrix-conduit/local
ExecStart=/usr/sbin/matrix-conduit ExecStart=/usr/sbin/matrix-conduit
Restart=on-failure Restart=on-failure
+50 -38
View File
@@ -4,8 +4,8 @@ set -e
. /usr/share/debconf/confmodule . /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_CONFIG_FILE="$CONDUIT_CONFIG_PATH/debian" CONDUIT_CONFIG_FILE="${CONDUIT_CONFIG_PATH}/conduit.toml"
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit/conduit_db
case "$1" in case "$1" in
configure) configure)
@@ -13,7 +13,7 @@ case "$1" in
if ! getent passwd _matrix-conduit > /dev/null ; then if ! getent passwd _matrix-conduit > /dev/null ; then
echo 'Adding system user for the Conduit Matrix homeserver' 1>&2 echo 'Adding system user for the Conduit Matrix homeserver' 1>&2
adduser --system --group --quiet \ adduser --system --group --quiet \
--home $CONDUIT_DATABASE_PATH \ --home "$CONDUIT_DATABASE_PATH" \
--disabled-login \ --disabled-login \
--force-badname \ --force-badname \
_matrix-conduit _matrix-conduit
@@ -25,48 +25,60 @@ case "$1" in
chown _matrix-conduit "$CONDUIT_DATABASE_PATH" chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
fi fi
# Write the debconf values in the config. if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
db_get matrix-conduit/hostname # Write the debconf values in the config.
ROCKET_SERVER_NAME="$RET" db_get matrix-conduit/hostname
db_get matrix-conduit/address CONDUIT_SERVER_NAME="$RET"
ROCKET_ADDRESS="$RET" db_get matrix-conduit/address
db_get matrix-conduit/port CONDUIT_ADDRESS="$RET"
ROCKET_PORT="$RET" db_get matrix-conduit/port
cat >"$CONDUIT_CONFIG_FILE" << EOF CONDUIT_PORT="$RET"
# Conduit homeserver Debian configuration mkdir -p "$CONDUIT_CONFIG_PATH"
# cat > "$CONDUIT_CONFIG_FILE" << EOF
# Conduit is an application based on the Rocket web framework. [global]
# Configuration of Conduit happens via Debconf (of which the resulting config # The server_name is the name of this server. It is used as a suffix for user
# is in this file) and optionally by uncommenting and tweaking the variables in # and room ids. Examples: matrix.org, conduit.rs
# /etc/matrix-conduit/local. # The Conduit server needs to be reachable at https://your.server.name/ on port
# 443 (client-server) and 8448 (federation) OR you can create /.well-known
# files to redirect requests. See
# https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
# and https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server
# for more information.
server_name = "${CONDUIT_SERVER_NAME}"
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN! # This is the only directory where Conduit will save its data.
# database_path = "${CONDUIT_DATABASE_PATH}"
# Please make changes by running:
#
# \$ dpkg-reconfigure matrix-conduit
#
# or by providing overriding changes in /etc/matrix-conduit/local.
# The server (host)name of the Matrix homeserver. # The address Conduit will be listening on.
#
# This is the hostname the homeserver will be reachable at via a client.
ROCKET_SERVER_NAME="$ROCKET_SERVER_NAME"
# The address the Matrix homeserver listens on.
#
# By default the server listens on address 0.0.0.0. Change this to 127.0.0.1 to # By default the server listens on address 0.0.0.0. Change this to 127.0.0.1 to
# only listen on the localhost when using a reverse proxy. # only listen on the localhost when using a reverse proxy.
ROCKET_ADDRESS="$ROCKET_ADDRESS" address = "${CONDUIT_ADDRESS}"
# The port of the Matrix homeserver. # The port Conduit will be running on. You need to set up a reverse proxy in
# # your web server (e.g. apache or nginx), so all requests to /_matrix on port
# This port is could be any available port if accessed by a reverse proxy. # 443 and 8448 will be forwarded to the Conduit instance running on this port.
# By default the server listens on port 8000. port = ${CONDUIT_PORT}
ROCKET_PORT="$ROCKET_PORT"
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN! # Max size for uploads
max_request_size = 20_000_000 # in bytes
# Disable registration. No new users will be able to register on this server.
#allow_registration = false
# Disable encryption, so no new encrypted rooms can be created.
# Note: Existing rooms will continue to work.
#allow_encryption = false
#allow_federation = false
# Enable jaeger to support monitoring and troubleshooting through jaeger.
#allow_jaeger = false
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
#log = "info,state_res=warn,rocket=off,_=off,sled=off"
#workers = 4 # default: cpu core count * 2
EOF EOF
fi
;; ;;
esac esac
+5
View File
@@ -1,11 +1,16 @@
#!/bin/sh #!/bin/sh
set -e set -e
. /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
case $1 in case $1 in
purge) purge)
# Remove debconf changes from the db
db_purge
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior # Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
# "configuration files must be preserved when the package is removed, and # "configuration files must be preserved when the package is removed, and
# only deleted when the package is purged." # only deleted when the package is purged."
+1 -1
View File
@@ -16,6 +16,6 @@ Description: The listen address of the Matrix homeserver
Template: matrix-conduit/port Template: matrix-conduit/port
Type: string Type: string
Default: 14004 Default: 6167
Description: The port of the Matrix homeserver Description: The port of the Matrix homeserver
This port is most often just accessed by a reverse proxy. This port is most often just accessed by a reverse proxy.
+26 -21
View File
@@ -5,43 +5,48 @@ services:
homeserver: homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image, ### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### then you are ready to go. ### then you are ready to go.
image: matrixconduit/matrix-conduit:latest #image: matrixconduit/matrix-conduit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the ### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this: ### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d ### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d
# build: build:
# context: . context: .
# args: args:
# CREATED: CREATED: '2021-03-16T08:18:27Z'
# VERSION: VERSION: '0.1.0'
# LOCAL: 'false' LOCAL: 'false'
# GIT_REF: HEAD GIT_REF: origin/master
restart: unless-stopped restart: unless-stopped
ports: ports:
- 8448:8000 - 8448:8000
volumes: volumes:
- db:/srv/conduit/.local/share/conduit - db:/srv/conduit/.local/share/conduit
### Uncomment if you want to use Rocket.toml to configure Conduit ### Uncomment if you want to use conduit.toml to configure Conduit
### Note: Set env vars will override Rocket.toml values ### Note: Set env vars will override conduit.toml values
# - ./Rocket.toml:/srv/conduit/Rocket.toml # - ./conduit.toml:/srv/conduit/conduit.toml
environment: environment:
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
### Uncomment and change values as desired ### Uncomment and change values as desired
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical # CONDUIT_ADDRESS: 127.0.0.1
# ROCKET_PORT: 8000 # CONDUIT_PORT: 8000
# ROCKET_REGISTRATION_DISABLED: 'true' # CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if you want to configure purely by env vars, set this to an empty string ''
# ROCKET_ENCRYPTION_DISABLED: 'true' # Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# ROCKET_FEDERATION_ENABLED: 'true' # CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit # CONDUIT_ALLOW_JAEGER: 'false'
# ROCKET_WORKERS: 10 # CONDUIT_ALLOW_REGISTRATION : 'false'
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB # CONDUIT_ALLOW_ENCRYPTION: 'false'
# CONDUIT_ALLOW_FEDERATION: 'false'
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
# CONDUIT_WORKERS: 10
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
### Uncomment if you want to use your own Element-Web App. ### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second ### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit ### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md ### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web: # element-web:
# image: vectorim/riot-web:latest # image: vectorim/element-web:latest
# restart: unless-stopped # restart: unless-stopped
# ports: # ports:
# - 8009:80 # - 8009:80
+6 -4
View File
@@ -17,13 +17,13 @@ The Dockerfile includes a few build arguments that should be supplied when build
ARG LOCAL=false ARG LOCAL=false
ARG CREATED ARG CREATED
ARG VERSION ARG VERSION
ARG GIT_REF=HEAD ARG GIT_REF=origin/master
``` ```
- **CREATED:** Date and time as string (date-time as defined by RFC 3339). Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.created`. Supply by it like this `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` - **CREATED:** Date and time as string (date-time as defined by RFC 3339). Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.created`. Supply by it like this `$(date -u +'%Y-%m-%dT%H:%M:%SZ')`
- **VERSION:** The SemVer version of Conduit, which is in the image. Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.version`. If you have a `Cargo.toml` in your build context, you can get it with `$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)` - **VERSION:** The SemVer version of Conduit, which is in the image. Will be used to create the Open Container Initiative compliant label `org.opencontainers.image.version`. If you have a `Cargo.toml` in your build context, you can get it with `$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml)`
- **LOCAL:** *(Optional)* A boolean value, specifies if the local build context should be used, or if the official repository will be cloned. If not supplied with the build command, it will default to `false`. - **LOCAL:** *(Optional)* A boolean value, specifies if the local build context should be used, or if the official repository will be cloned. If not supplied with the build command, it will default to `false`.
- **GIT_REF:** *(Optional)* A git ref, like `HEAD` or a commit ID. The supplied ref will be used to create the Open Container Initiative compliant label `org.opencontainers.image.revision` and will be the ref that is cloned from the repository when not building from the local context. If not supplied with the build command, it will default to `HEAD`. - **GIT_REF:** *(Optional)* A git ref, like `HEAD` or a commit ID. The supplied ref will be used to create the Open Container Initiative compliant label `org.opencontainers.image.revision` and will be the ref that is cloned from the repository when not building from the local context. If not supplied with the build command, it will default to `origin/master`.
To build the image you can use the following command To build the image you can use the following command
@@ -40,10 +40,12 @@ which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`
After building the image you can simply run it with After building the image you can simply run it with
``` bash ``` bash
docker run -d -p 8448:8000 -v db:/srv/conduit/.local/share/conduit -e ROCKET_SERVER_NAME="localhost:8000" matrixconduit/matrix-conduit:latest docker run -d -p 8448:8000 -v ~/conduit.toml:/srv/conduit/conduit.toml -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
``` ```
For detached mode, you also need to use the `-d` flag. You can pass in more env vars as are shown here, for an overview of possible values, you can take a look at the `docker-compose.yml` file. For detached mode, you also need to use the `-d` flag. You also need to supply a `conduit.toml` config file, you can find an example [here](../conduit-example.toml).
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
too pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
If you just want to test Conduit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it. If you just want to test Conduit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
+19 -13
View File
@@ -15,32 +15,38 @@ services:
# CREATED: # CREATED:
# VERSION: # VERSION:
# LOCAL: 'false' # LOCAL: 'false'
# GIT_REF: HEAD # GIT_REF: origin/master
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- db:/srv/conduit/.local/share/conduit - db:/srv/conduit/.local/share/conduit
### Uncomment if you want to use Rocket.toml to configure Conduit ### Uncomment if you want to use conduit.toml to configure Conduit
### Note: Set env vars will override Rocket.toml values ### Note: Set env vars will override conduit.toml values
# - ./Rocket.toml:/srv/conduit/Rocket.toml # - ./conduit.toml:/srv/conduit/conduit.toml
networks: networks:
- proxy - proxy
environment: environment:
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
### Uncomment and change values as desired ### Uncomment and change values as desired
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical # CONDUIT_ADDRESS: 127.0.0.1
# ROCKET_PORT: 8000 # CONDUIT_PORT: 8000
# ROCKET_REGISTRATION_DISABLED: 'true' # CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if want to configure purely by env vars, set this to an empty string ''
# ROCKET_ENCRYPTION_DISABLED: 'true' # Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit # CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
# ROCKET_WORKERS: 10 # CONDUIT_ALLOW_JAEGER: 'false'
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB # CONDUIT_ALLOW_REGISTRATION : 'false'
# CONDUIT_ALLOW_ENCRYPTION: 'false'
# CONDUIT_ALLOW_FEDERATION: 'false'
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
# CONDUIT_WORKERS: 10
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
### Uncomment if you want to use your own Element-Web App. ### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second ### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit ### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md ### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web: # element-web:
# image: vectorim/riot-web:latest # image: vectorim/element-web:latest
# restart: unless-stopped # restart: unless-stopped
# volumes: # volumes:
# - ./element_config.json:/app/config.json # - ./element_config.json:/app/config.json
+1 -1
View File
@@ -1 +1 @@
1.50.0 1.51.0
+18 -21
View File
@@ -1,11 +1,11 @@
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use bytes::BytesMut; use bytes::BytesMut;
use http::header::{HeaderValue, CONTENT_TYPE};
use log::warn; use log::warn;
use ruma::api::{IncomingResponse, OutgoingRequest, SendAccessToken}; use ruma::api::{IncomingResponse, OutgoingRequest, SendAccessToken};
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt::Debug, fmt::Debug,
mem,
time::Duration, time::Duration,
}; };
@@ -40,33 +40,30 @@ where
); );
*http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid"); *http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid");
http_request.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_str("application/json").unwrap(),
);
let mut reqwest_request = reqwest::Request::try_from(http_request) let mut reqwest_request = reqwest::Request::try_from(http_request)
.expect("all http requests are valid reqwest requests"); .expect("all http requests are valid reqwest requests");
*reqwest_request.timeout_mut() = Some(Duration::from_secs(30)); *reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
let url = reqwest_request.url().clone(); let url = reqwest_request.url().clone();
let mut reqwest_response = globals.reqwest_client().execute(reqwest_request).await?; let mut response = globals
.reqwest_client()
.execute(reqwest_request)
.await?;
// Because reqwest::Response -> http::Response is complicated: // reqwest::Response -> http::Response conversion
let status = reqwest_response.status(); let status = response.status();
let mut http_response = http::Response::builder().status(status); let mut http_response_builder = http::Response::builder()
let headers = http_response.headers_mut().unwrap(); .status(status)
.version(response.version());
mem::swap(
response.headers_mut(),
http_response_builder
.headers_mut()
.expect("http::response::Builder is usable"),
);
for (k, v) in reqwest_response.headers_mut().drain() { let body = response.bytes().await.unwrap_or_else(|e| {
if let Some(key) = k {
headers.insert(key, v);
}
}
let status = reqwest_response.status();
let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
warn!("server error: {}", e); warn!("server error: {}", e);
Vec::new().into() Vec::new().into()
}); // TODO: handle timeout }); // TODO: handle timeout
@@ -82,7 +79,7 @@ where
} }
let response = T::IncomingResponse::try_from_http_response( let response = T::IncomingResponse::try_from_http_response(
http_response http_response_builder
.body(body) .body(body)
.expect("reqwest body is valid http body"), .expect("reqwest body is valid http body"),
); );
+42 -30
View File
@@ -1,4 +1,4 @@
use std::{collections::BTreeMap, convert::TryInto}; use std::{collections::BTreeMap, convert::TryInto, sync::Arc};
use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma}; use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
@@ -42,7 +42,7 @@ const GUEST_NAME_LENGTH: usize = 10;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_register_available_route( pub async fn get_register_available_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_username_availability::Request<'_>>, body: Ruma<get_username_availability::Request<'_>>,
) -> ConduitResult<get_username_availability::Response> { ) -> ConduitResult<get_username_availability::Response> {
// Validate user id // Validate user id
@@ -85,10 +85,10 @@ pub async fn get_register_available_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn register_route( pub async fn register_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<register::Request<'_>>, body: Ruma<register::Request<'_>>,
) -> ConduitResult<register::Response> { ) -> ConduitResult<register::Response> {
if !db.globals.allow_registration() { if !db.globals.allow_registration() && !body.from_appservice {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
"Registration has been disabled.", "Registration has been disabled.",
@@ -143,16 +143,28 @@ pub async fn register_route(
if !body.from_appservice { if !body.from_appservice {
if let Some(auth) = &body.auth { if let Some(auth) = &body.auth {
let (worked, uiaainfo) = let (worked, uiaainfo) = db.uiaa.try_auth(
db.uiaa &UserId::parse_with_server_name("", db.globals.server_name())
.try_auth(&user_id, "".into(), auth, &uiaainfo, &db.users, &db.globals)?; .expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
&db.users,
&db.globals,
)?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&user_id, "".into(), &uiaainfo)?; db.uiaa.create(
&UserId::parse_with_server_name("", db.globals.server_name())
.expect("we know this is valid"),
"".into(),
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
} }
@@ -167,12 +179,11 @@ pub async fn register_route(
let password = if is_guest { let password = if is_guest {
None None
} else { } else {
body.password.clone() body.password.as_deref()
} };
.unwrap_or_default();
// Create user // Create user
db.users.create(&user_id, &password)?; db.users.create(&user_id, password)?;
// Initial data // Initial data
db.account_data.update( db.account_data.update(
@@ -216,12 +227,12 @@ pub async fn register_route(
)?; )?;
// If this is the first user on this server, create the admins room // If this is the first user on this server, create the admins room
if db.users.count() == 1 { if db.users.count()? == 1 {
// Create a user for the server // Create a user for the server
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name()) let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is valid"); .expect("@conduit:server_name is valid");
db.users.create(&conduit_user, "")?; db.users.create(&conduit_user, None)?;
let room_id = RoomId::new(db.globals.server_name()); let room_id = RoomId::new(db.globals.server_name());
@@ -275,18 +286,8 @@ pub async fn register_route(
event_type: EventType::RoomPowerLevels, event_type: EventType::RoomPowerLevels,
content: serde_json::to_value( content: serde_json::to_value(
ruma::events::room::power_levels::PowerLevelsEventContent { ruma::events::room::power_levels::PowerLevelsEventContent {
ban: 50.into(),
events: BTreeMap::new(),
events_default: 0.into(),
invite: 50.into(),
kick: 50.into(),
redact: 50.into(),
state_default: 50.into(),
users, users,
users_default: 0.into(), ..Default::default()
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
room: 50.into(),
},
}, },
) )
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
@@ -495,7 +496,7 @@ pub async fn register_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn change_password_route( pub async fn change_password_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<change_password::Request<'_>>, body: Ruma<change_password::Request<'_>>,
) -> ConduitResult<change_password::Response> { ) -> ConduitResult<change_password::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -526,11 +527,17 @@ pub async fn change_password_route(
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
db.users.set_password(&sender_user, &body.new_password)?; db.users
.set_password(&sender_user, Some(&body.new_password))?;
if body.logout_devices { if body.logout_devices {
// Logout all devices except the current one // Logout all devices except the current one
@@ -581,7 +588,7 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn deactivate_route( pub async fn deactivate_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<deactivate::Request<'_>>, body: Ruma<deactivate::Request<'_>>,
) -> ConduitResult<deactivate::Response> { ) -> ConduitResult<deactivate::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -612,7 +619,12 @@ pub async fn deactivate_route(
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
+8 -11
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use regex::Regex; use regex::Regex;
@@ -22,7 +24,7 @@ use rocket::{delete, get, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn create_alias_route( pub async fn create_alias_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_alias::Request<'_>>, body: Ruma<create_alias::Request<'_>>,
) -> ConduitResult<create_alias::Response> { ) -> ConduitResult<create_alias::Response> {
if db.rooms.id_from_alias(&body.room_alias)?.is_some() { if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
@@ -43,7 +45,7 @@ pub async fn create_alias_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_alias_route( pub async fn delete_alias_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_alias::Request<'_>>, body: Ruma<delete_alias::Request<'_>>,
) -> ConduitResult<delete_alias::Response> { ) -> ConduitResult<delete_alias::Response> {
db.rooms.set_alias(&body.room_alias, None, &db.globals)?; db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
@@ -59,7 +61,7 @@ pub async fn delete_alias_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_alias_route( pub async fn get_alias_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_alias::Request<'_>>, body: Ruma<get_alias::Request<'_>>,
) -> ConduitResult<get_alias::Response> { ) -> ConduitResult<get_alias::Response> {
get_alias_helper(&db, &body.room_alias).await get_alias_helper(&db, &body.room_alias).await
@@ -86,7 +88,8 @@ pub async fn get_alias_helper(
match db.rooms.id_from_alias(&room_alias)? { match db.rooms.id_from_alias(&room_alias)? {
Some(r) => room_id = Some(r), Some(r) => room_id = Some(r),
None => { None => {
for (_id, registration) in db.appservice.iter_all().filter_map(|r| r.ok()) { let iter = db.appservice.iter_all()?;
for (_id, registration) in iter.filter_map(|r| r.ok()) {
let aliases = registration let aliases = registration
.get("namespaces") .get("namespaces")
.and_then(|ns| ns.get("aliases")) .and_then(|ns| ns.get("aliases"))
@@ -94,13 +97,7 @@ pub async fn get_alias_helper(
.map_or_else(Vec::new, |aliases| { .map_or_else(Vec::new, |aliases| {
aliases aliases
.iter() .iter()
.map(|aliases| { .filter_map(|aliases| Regex::new(aliases.get("regex")?.as_str()?).ok())
aliases
.get("regex")
.and_then(|regex| regex.as_str())
.and_then(|regex| Regex::new(regex).ok())
})
.filter_map(|o| o)
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
+17 -15
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{ use ruma::api::client::{
@@ -19,7 +21,7 @@ use rocket::{delete, get, post, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn create_backup_route( pub async fn create_backup_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_backup::Request>, body: Ruma<create_backup::Request>,
) -> ConduitResult<create_backup::Response> { ) -> ConduitResult<create_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -38,7 +40,7 @@ pub async fn create_backup_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn update_backup_route( pub async fn update_backup_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<update_backup::Request<'_>>, body: Ruma<update_backup::Request<'_>>,
) -> ConduitResult<update_backup::Response> { ) -> ConduitResult<update_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -56,7 +58,7 @@ pub async fn update_backup_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_latest_backup_route( pub async fn get_latest_backup_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_latest_backup::Request>, body: Ruma<get_latest_backup::Request>,
) -> ConduitResult<get_latest_backup::Response> { ) -> ConduitResult<get_latest_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -84,7 +86,7 @@ pub async fn get_latest_backup_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_backup_route( pub async fn get_backup_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_backup::Request<'_>>, body: Ruma<get_backup::Request<'_>>,
) -> ConduitResult<get_backup::Response> { ) -> ConduitResult<get_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -111,7 +113,7 @@ pub async fn get_backup_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_backup_route( pub async fn delete_backup_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_backup::Request<'_>>, body: Ruma<delete_backup::Request<'_>>,
) -> ConduitResult<delete_backup::Response> { ) -> ConduitResult<delete_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -130,7 +132,7 @@ pub async fn delete_backup_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn add_backup_keys_route( pub async fn add_backup_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<add_backup_keys::Request<'_>>, body: Ruma<add_backup_keys::Request<'_>>,
) -> ConduitResult<add_backup_keys::Response> { ) -> ConduitResult<add_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -164,7 +166,7 @@ pub async fn add_backup_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn add_backup_key_sessions_route( pub async fn add_backup_key_sessions_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<add_backup_key_sessions::Request<'_>>, body: Ruma<add_backup_key_sessions::Request<'_>>,
) -> ConduitResult<add_backup_key_sessions::Response> { ) -> ConduitResult<add_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -196,7 +198,7 @@ pub async fn add_backup_key_sessions_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn add_backup_key_session_route( pub async fn add_backup_key_session_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<add_backup_key_session::Request<'_>>, body: Ruma<add_backup_key_session::Request<'_>>,
) -> ConduitResult<add_backup_key_session::Response> { ) -> ConduitResult<add_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -225,7 +227,7 @@ pub async fn add_backup_key_session_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_backup_keys_route( pub async fn get_backup_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_backup_keys::Request<'_>>, body: Ruma<get_backup_keys::Request<'_>>,
) -> ConduitResult<get_backup_keys::Response> { ) -> ConduitResult<get_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -241,14 +243,14 @@ pub async fn get_backup_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_backup_key_sessions_route( pub async fn get_backup_key_sessions_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_backup_key_sessions::Request<'_>>, body: Ruma<get_backup_key_sessions::Request<'_>>,
) -> ConduitResult<get_backup_key_sessions::Response> { ) -> ConduitResult<get_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sessions = db let sessions = db
.key_backups .key_backups
.get_room(&sender_user, &body.version, &body.room_id); .get_room(&sender_user, &body.version, &body.room_id)?;
Ok(get_backup_key_sessions::Response { sessions }.into()) Ok(get_backup_key_sessions::Response { sessions }.into())
} }
@@ -259,7 +261,7 @@ pub async fn get_backup_key_sessions_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_backup_key_session_route( pub async fn get_backup_key_session_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_backup_key_session::Request<'_>>, body: Ruma<get_backup_key_session::Request<'_>>,
) -> ConduitResult<get_backup_key_session::Response> { ) -> ConduitResult<get_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -281,7 +283,7 @@ pub async fn get_backup_key_session_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_backup_keys_route( pub async fn delete_backup_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_backup_keys::Request<'_>>, body: Ruma<delete_backup_keys::Request<'_>>,
) -> ConduitResult<delete_backup_keys::Response> { ) -> ConduitResult<delete_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -304,7 +306,7 @@ pub async fn delete_backup_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_backup_key_sessions_route( pub async fn delete_backup_key_sessions_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_backup_key_sessions::Request<'_>>, body: Ruma<delete_backup_key_sessions::Request<'_>>,
) -> ConduitResult<delete_backup_key_sessions::Response> { ) -> ConduitResult<delete_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -327,7 +329,7 @@ pub async fn delete_backup_key_sessions_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_backup_key_session_route( pub async fn delete_backup_key_session_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_backup_key_session::Request<'_>>, body: Ruma<delete_backup_key_session::Request<'_>>,
) -> ConduitResult<delete_backup_key_session::Response> { ) -> ConduitResult<delete_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+43 -21
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{ use ruma::{
@@ -8,9 +10,11 @@ use ruma::{
set_room_account_data, set_room_account_data,
}, },
}, },
events::{custom::CustomEventContent, BasicEvent}, events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
serde::Raw, serde::Raw,
}; };
use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; use rocket::{get, put};
@@ -21,12 +25,12 @@ use rocket::{get, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_global_account_data_route( pub async fn set_global_account_data_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_global_account_data::Request<'_>>, body: Ruma<set_global_account_data::Request<'_>>,
) -> ConduitResult<set_global_account_data::Response> { ) -> ConduitResult<set_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str(body.data.get()) let data = serde_json::from_str::<serde_json::Value>(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string(); let event_type = body.event_type.to_string();
@@ -35,9 +39,10 @@ pub async fn set_global_account_data_route(
None, None,
sender_user, sender_user,
event_type.clone().into(), event_type.clone().into(),
&BasicEvent { &json!({
content: CustomEventContent { event_type, data }, "type": event_type,
}, "content": data,
}),
&db.globals, &db.globals,
)?; )?;
@@ -55,12 +60,12 @@ pub async fn set_global_account_data_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_room_account_data_route( pub async fn set_room_account_data_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_room_account_data::Request<'_>>, body: Ruma<set_room_account_data::Request<'_>>,
) -> ConduitResult<set_room_account_data::Response> { ) -> ConduitResult<set_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str(body.data.get()) let data = serde_json::from_str::<serde_json::Value>(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string(); let event_type = body.event_type.to_string();
@@ -69,9 +74,10 @@ pub async fn set_room_account_data_route(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
event_type.clone().into(), event_type.clone().into(),
&BasicEvent { &json!({
content: CustomEventContent { event_type, data }, "type": event_type,
}, "content": data,
}),
&db.globals, &db.globals,
)?; )?;
@@ -86,19 +92,22 @@ pub async fn set_room_account_data_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_global_account_data_route( pub async fn get_global_account_data_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_global_account_data::Request<'_>>, body: Ruma<get_global_account_data::Request<'_>>,
) -> ConduitResult<get_global_account_data::Response> { ) -> ConduitResult<get_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = db let event = db
.account_data .account_data
.get::<Raw<ruma::events::AnyBasicEvent>>(None, sender_user, body.event_type.clone().into())? .get::<Box<RawJsonValue>>(None, sender_user, body.event_type.clone().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?; db.flush().await?;
Ok(get_global_account_data::Response { account_data: data }.into()) let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
Ok(get_global_account_data::Response { account_data }.into())
} }
#[cfg_attr( #[cfg_attr(
@@ -110,21 +119,34 @@ pub async fn get_global_account_data_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_room_account_data_route( pub async fn get_room_account_data_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_room_account_data::Request<'_>>, body: Ruma<get_room_account_data::Request<'_>>,
) -> ConduitResult<get_room_account_data::Response> { ) -> ConduitResult<get_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = db let event = db
.account_data .account_data
.get::<Raw<ruma::events::AnyBasicEvent>>( .get::<Box<RawJsonValue>>(
Some(&body.room_id), Some(&body.room_id),
sender_user, sender_user,
body.event_type.clone().into(), body.event_type.clone().into(),
)? )?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?; db.flush().await?;
Ok(get_room_account_data::Response { account_data: data }.into()) let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
Ok(get_room_account_data::Response { account_data }.into())
}
#[derive(Deserialize)]
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
} }
+2 -2
View File
@@ -1,7 +1,7 @@
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{error::ErrorKind, r0::context::get_context}; use ruma::api::client::{error::ErrorKind, r0::context::get_context};
use std::convert::TryFrom; use std::{convert::TryFrom, sync::Arc};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::get; use rocket::get;
@@ -12,7 +12,7 @@ use rocket::get;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_context_route( pub async fn get_context_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_context::Request<'_>>, body: Ruma<get_context::Request<'_>>,
) -> ConduitResult<get_context::Response> { ) -> ConduitResult<get_context::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+19 -7
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{utils, ConduitResult, Database, Error, Ruma}; use crate::{utils, ConduitResult, Database, Error, Ruma};
use ruma::api::client::{ use ruma::api::client::{
@@ -18,7 +20,7 @@ use rocket::{delete, get, post, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_devices_route( pub async fn get_devices_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_devices::Request>, body: Ruma<get_devices::Request>,
) -> ConduitResult<get_devices::Response> { ) -> ConduitResult<get_devices::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -38,7 +40,7 @@ pub async fn get_devices_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_device_route( pub async fn get_device_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_device::Request<'_>>, body: Ruma<get_device::Request<'_>>,
) -> ConduitResult<get_device::Response> { ) -> ConduitResult<get_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -57,7 +59,7 @@ pub async fn get_device_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn update_device_route( pub async fn update_device_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<update_device::Request<'_>>, body: Ruma<update_device::Request<'_>>,
) -> ConduitResult<update_device::Response> { ) -> ConduitResult<update_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -83,7 +85,7 @@ pub async fn update_device_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_device_route( pub async fn delete_device_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_device::Request<'_>>, body: Ruma<delete_device::Request<'_>>,
) -> ConduitResult<delete_device::Response> { ) -> ConduitResult<delete_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -115,7 +117,12 @@ pub async fn delete_device_route(
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
@@ -132,7 +139,7 @@ pub async fn delete_device_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_devices_route( pub async fn delete_devices_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_devices::Request<'_>>, body: Ruma<delete_devices::Request<'_>>,
) -> ConduitResult<delete_devices::Response> { ) -> ConduitResult<delete_devices::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -164,7 +171,12 @@ pub async fn delete_devices_route(
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
+47 -20
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Result, Ruma}; use crate::{ConduitResult, Database, Error, Result, Ruma};
use log::info; use log::info;
@@ -33,7 +35,7 @@ use rocket::{get, post, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_public_rooms_filtered_route( pub async fn get_public_rooms_filtered_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_public_rooms_filtered::Request<'_>>, body: Ruma<get_public_rooms_filtered::Request<'_>>,
) -> ConduitResult<get_public_rooms_filtered::Response> { ) -> ConduitResult<get_public_rooms_filtered::Response> {
get_public_rooms_filtered_helper( get_public_rooms_filtered_helper(
@@ -53,7 +55,7 @@ pub async fn get_public_rooms_filtered_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_public_rooms_route( pub async fn get_public_rooms_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_public_rooms::Request<'_>>, body: Ruma<get_public_rooms::Request<'_>>,
) -> ConduitResult<get_public_rooms::Response> { ) -> ConduitResult<get_public_rooms::Response> {
let response = get_public_rooms_filtered_helper( let response = get_public_rooms_filtered_helper(
@@ -82,7 +84,7 @@ pub async fn get_public_rooms_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_room_visibility_route( pub async fn set_room_visibility_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_room_visibility::Request<'_>>, body: Ruma<set_room_visibility::Request<'_>>,
) -> ConduitResult<set_room_visibility::Response> { ) -> ConduitResult<set_room_visibility::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -112,7 +114,7 @@ pub async fn set_room_visibility_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_room_visibility_route( pub async fn get_room_visibility_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_room_visibility::Request<'_>>, body: Ruma<get_room_visibility::Request<'_>>,
) -> ConduitResult<get_room_visibility::Response> { ) -> ConduitResult<get_room_visibility::Response> {
Ok(get_room_visibility::Response { Ok(get_room_visibility::Response {
@@ -133,9 +135,7 @@ pub async fn get_public_rooms_filtered_helper(
filter: &IncomingFilter, filter: &IncomingFilter,
_network: &IncomingRoomNetwork, _network: &IncomingRoomNetwork,
) -> ConduitResult<get_public_rooms_filtered::Response> { ) -> ConduitResult<get_public_rooms_filtered::Response> {
if let Some(other_server) = server if let Some(other_server) = server.filter(|server| *server != db.globals.server_name().as_str())
.clone()
.filter(|server| *server != db.globals.server_name().as_str())
{ {
let response = db let response = db
.sending .sending
@@ -160,15 +160,12 @@ pub async fn get_public_rooms_filtered_helper(
.map(|c| { .map(|c| {
// Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk // Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
// to ruma::api::client::r0::directory::PublicRoomsChunk // to ruma::api::client::r0::directory::PublicRoomsChunk
Ok::<_, Error>( serde_json::from_str(
serde_json::from_str( &serde_json::to_string(&c)
&serde_json::to_string(&c) .expect("PublicRoomsChunk::to_string always works"),
.expect("PublicRoomsChunk::to_string always works"),
)
.expect("federation and client-server PublicRoomsChunk are the same type"),
) )
.expect("federation and client-server PublicRoomsChunk are the same type")
}) })
.filter_map(|r| r.ok())
.collect(), .collect(),
prev_batch: response.prev_batch, prev_batch: response.prev_batch,
next_batch: response.next_batch, next_batch: response.next_batch,
@@ -217,7 +214,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(None), |s| { .map_or(Ok::<_, Error>(None), |s| {
Ok(serde_json::from_value::< Ok(serde_json::from_value::<
Raw<canonical_alias::CanonicalAliasEventContent>, Raw<canonical_alias::CanonicalAliasEventContent>,
>(s.content) >(s.content.clone())
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
.map_err(|_| { .map_err(|_| {
@@ -230,7 +227,7 @@ pub async fn get_public_rooms_filtered_helper(
.room_state_get(&room_id, &EventType::RoomName, "")? .room_state_get(&room_id, &EventType::RoomName, "")?
.map_or(Ok::<_, Error>(None), |s| { .map_or(Ok::<_, Error>(None), |s| {
Ok(serde_json::from_value::<Raw<name::NameEventContent>>( Ok(serde_json::from_value::<Raw<name::NameEventContent>>(
s.content, s.content.clone(),
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
@@ -247,7 +244,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(None), |s| { .map_or(Ok::<_, Error>(None), |s| {
Ok(Some( Ok(Some(
serde_json::from_value::<Raw<topic::TopicEventContent>>( serde_json::from_value::<Raw<topic::TopicEventContent>>(
s.content, s.content.clone(),
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
@@ -263,7 +260,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(false), |s| { .map_or(Ok::<_, Error>(false), |s| {
Ok(serde_json::from_value::< Ok(serde_json::from_value::<
Raw<history_visibility::HistoryVisibilityEventContent>, Raw<history_visibility::HistoryVisibilityEventContent>,
>(s.content) >(s.content.clone())
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
.map_err(|_| { .map_err(|_| {
@@ -280,7 +277,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(false), |s| { .map_or(Ok::<_, Error>(false), |s| {
Ok( Ok(
serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>( serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>(
s.content, s.content.clone(),
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
@@ -297,7 +294,7 @@ pub async fn get_public_rooms_filtered_helper(
.map(|s| { .map(|s| {
Ok::<_, Error>( Ok::<_, Error>(
serde_json::from_value::<Raw<avatar::AvatarEventContent>>( serde_json::from_value::<Raw<avatar::AvatarEventContent>>(
s.content, s.content.clone(),
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
@@ -315,6 +312,36 @@ pub async fn get_public_rooms_filtered_helper(
Ok(chunk) Ok(chunk)
}) })
.filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms .filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
.filter(|chunk| {
if let Some(query) = filter
.generic_search_term
.as_ref()
.map(|q| q.to_lowercase())
{
if let Some(name) = &chunk.name {
if name.to_lowercase().contains(&query) {
return true;
}
}
if let Some(topic) = &chunk.topic {
if topic.to_lowercase().contains(&query) {
return true;
}
}
if let Some(canonical_alias) = &chunk.canonical_alias {
if canonical_alias.as_str().to_lowercase().contains(&query) {
return true;
}
}
false
} else {
// No search term
true
}
})
// We need to collect all, so we can sort by member count // We need to collect all, so we can sort by member count
.collect::<Vec<_>>(); .collect::<Vec<_>>();
+131 -96
View File
@@ -1,5 +1,5 @@
use super::{State, SESSION_ID_LENGTH}; use super::{State, SESSION_ID_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Ruma}; use crate::{utils, ConduitResult, Database, Error, Result, Ruma};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -12,8 +12,12 @@ use ruma::{
}, },
}, },
encryption::UnsignedDeviceInfo, encryption::UnsignedDeviceInfo,
DeviceId, DeviceKeyAlgorithm, UserId,
};
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
}; };
use std::collections::{BTreeMap, HashSet};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; use rocket::{get, post};
@@ -24,7 +28,7 @@ use rocket::{get, post};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn upload_keys_route( pub async fn upload_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<upload_keys::Request>, body: Ruma<upload_keys::Request>,
) -> ConduitResult<upload_keys::Response> { ) -> ConduitResult<upload_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -73,79 +77,19 @@ pub async fn upload_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_keys_route( pub async fn get_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_keys::Request<'_>>, body: Ruma<get_keys::Request<'_>>,
) -> ConduitResult<get_keys::Response> { ) -> ConduitResult<get_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut master_keys = BTreeMap::new(); let response = get_keys_helper(
let mut self_signing_keys = BTreeMap::new(); Some(sender_user),
let mut user_signing_keys = BTreeMap::new(); &body.device_keys,
let mut device_keys = BTreeMap::new(); |u| u == sender_user,
&db,
)?;
for (user_id, device_ids) in &body.device_keys { Ok(response.into())
if device_ids.is_empty() {
let mut container = BTreeMap::new();
for device_id in db.users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
let metadata = db
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| {
Error::bad_database("all_device_keys contained nonexistent device.")
})?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id, keys);
}
}
device_keys.insert(user_id.clone(), container);
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to get keys for nonexistent device.",
),
)?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id.clone(), keys);
}
device_keys.insert(user_id.clone(), container);
}
}
if let Some(master_key) = db.users.get_master_key(user_id, sender_user)? {
master_keys.insert(user_id.clone(), master_key);
}
if let Some(self_signing_key) = db.users.get_self_signing_key(user_id, sender_user)? {
self_signing_keys.insert(user_id.clone(), self_signing_key);
}
if user_id == sender_user {
if let Some(user_signing_key) = db.users.get_user_signing_key(sender_user)? {
user_signing_keys.insert(user_id.clone(), user_signing_key);
}
}
}
Ok(get_keys::Response {
master_keys,
self_signing_keys,
user_signing_keys,
device_keys,
failures: BTreeMap::new(),
}
.into())
} }
#[cfg_attr( #[cfg_attr(
@@ -154,32 +98,14 @@ pub async fn get_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn claim_keys_route( pub async fn claim_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<claim_keys::Request>, body: Ruma<claim_keys::Request>,
) -> ConduitResult<claim_keys::Response> { ) -> ConduitResult<claim_keys::Response> {
let mut one_time_keys = BTreeMap::new(); let response = claim_keys_helper(&body.one_time_keys, &db)?;
for (user_id, map) in &body.one_time_keys {
let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map {
if let Some(one_time_keys) =
db.users
.take_one_time_key(user_id, device_id, key_algorithm, &db.globals)?
{
let mut c = BTreeMap::new();
c.insert(one_time_keys.0, one_time_keys.1);
container.insert(device_id.clone(), c);
}
}
one_time_keys.insert(user_id.clone(), container);
}
db.flush().await?; db.flush().await?;
Ok(claim_keys::Response { Ok(response.into())
failures: BTreeMap::new(),
one_time_keys,
}
.into())
} }
#[cfg_attr( #[cfg_attr(
@@ -188,7 +114,7 @@ pub async fn claim_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn upload_signing_keys_route( pub async fn upload_signing_keys_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<upload_signing_keys::Request<'_>>, body: Ruma<upload_signing_keys::Request<'_>>,
) -> ConduitResult<upload_signing_keys::Response> { ) -> ConduitResult<upload_signing_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -220,7 +146,12 @@ pub async fn upload_signing_keys_route(
// Success! // Success!
} else { } else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
@@ -246,7 +177,7 @@ pub async fn upload_signing_keys_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn upload_signatures_route( pub async fn upload_signatures_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<upload_signatures::Request>, body: Ruma<upload_signatures::Request>,
) -> ConduitResult<upload_signatures::Response> { ) -> ConduitResult<upload_signatures::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -307,7 +238,7 @@ pub async fn upload_signatures_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_key_changes_route( pub async fn get_key_changes_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_key_changes::Request<'_>>, body: Ruma<get_key_changes::Request<'_>>,
) -> ConduitResult<get_key_changes::Response> { ) -> ConduitResult<get_key_changes::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -351,3 +282,107 @@ pub async fn get_key_changes_route(
} }
.into()) .into())
} }
pub fn get_keys_helper<F: Fn(&UserId) -> bool>(
sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>,
allowed_signatures: F,
db: &Database,
) -> Result<get_keys::Response> {
let mut master_keys = BTreeMap::new();
let mut self_signing_keys = BTreeMap::new();
let mut user_signing_keys = BTreeMap::new();
let mut device_keys = BTreeMap::new();
for (user_id, device_ids) in device_keys_input {
if device_ids.is_empty() {
let mut container = BTreeMap::new();
for device_id in db.users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
let metadata = db
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| {
Error::bad_database("all_device_keys contained nonexistent device.")
})?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id, keys);
}
}
device_keys.insert(user_id.clone(), container);
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to get keys for nonexistent device.",
),
)?;
keys.unsigned = UnsignedDeviceInfo {
device_display_name: metadata.display_name,
};
container.insert(device_id.clone(), keys);
}
device_keys.insert(user_id.clone(), container);
}
}
if let Some(master_key) = db.users.get_master_key(user_id, &allowed_signatures)? {
master_keys.insert(user_id.clone(), master_key);
}
if let Some(self_signing_key) = db
.users
.get_self_signing_key(user_id, &allowed_signatures)?
{
self_signing_keys.insert(user_id.clone(), self_signing_key);
}
if Some(user_id) == sender_user {
if let Some(user_signing_key) = db.users.get_user_signing_key(user_id)? {
user_signing_keys.insert(user_id.clone(), user_signing_key);
}
}
}
Ok(get_keys::Response {
master_keys,
self_signing_keys,
user_signing_keys,
device_keys,
failures: BTreeMap::new(),
})
}
pub fn claim_keys_helper(
one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
db: &Database,
) -> Result<claim_keys::Response> {
let mut one_time_keys = BTreeMap::new();
for (user_id, map) in one_time_keys_input {
let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map {
if let Some(one_time_keys) =
db.users
.take_one_time_key(user_id, device_id, key_algorithm, &db.globals)?
{
let mut c = BTreeMap::new();
c.insert(one_time_keys.0, one_time_keys.1);
container.insert(device_id.clone(), c);
}
}
one_time_keys.insert(user_id.clone(), container);
}
Ok(claim_keys::Response {
failures: BTreeMap::new(),
one_time_keys,
})
}
+56 -37
View File
@@ -7,14 +7,14 @@ use ruma::api::client::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; use rocket::{get, post};
use std::convert::TryInto; use std::{convert::TryInto, sync::Arc};
const MXC_LENGTH: usize = 32; const MXC_LENGTH: usize = 32;
#[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))]
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
pub async fn get_media_config_route( pub async fn get_media_config_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
) -> ConduitResult<get_media_config::Response> { ) -> ConduitResult<get_media_config::Response> {
Ok(get_media_config::Response { Ok(get_media_config::Response {
upload_size: db.globals.max_request_size().into(), upload_size: db.globals.max_request_size().into(),
@@ -28,7 +28,7 @@ pub async fn get_media_config_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn create_content_route( pub async fn create_content_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_content::Request<'_>>, body: Ruma<create_content::Request<'_>>,
) -> ConduitResult<create_content::Response> { ) -> ConduitResult<create_content::Response> {
let mxc = format!( let mxc = format!(
@@ -36,12 +36,20 @@ pub async fn create_content_route(
db.globals.server_name(), db.globals.server_name(),
utils::random_string(MXC_LENGTH) utils::random_string(MXC_LENGTH)
); );
db.media.create(
mxc.clone(), db.media
&body.filename.as_deref(), .create(
&body.content_type.as_deref(), mxc.clone(),
&body.file, &db.globals,
)?; &body
.filename
.as_ref()
.map(|filename| "inline; filename=".to_owned() + filename)
.as_deref(),
&body.content_type.as_deref(),
&body.file,
)
.await?;
db.flush().await?; db.flush().await?;
@@ -58,21 +66,21 @@ pub async fn create_content_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_content_route( pub async fn get_content_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_content::Request<'_>>, body: Ruma<get_content::Request<'_>>,
) -> ConduitResult<get_content::Response> { ) -> ConduitResult<get_content::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta { if let Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file, file,
}) = db.media.get(&mxc)? }) = db.media.get(&db.globals, &mxc).await?
{ {
Ok(get_content::Response { Ok(get_content::Response {
file, file,
content_type, content_type,
content_disposition: filename, content_disposition,
} }
.into()) .into())
} else if &*body.server_name != db.globals.server_name() && body.allow_remote { } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
@@ -89,12 +97,15 @@ pub async fn get_content_route(
) )
.await?; .await?;
db.media.create( db.media
mxc, .create(
&get_content_response.content_disposition.as_deref(), mxc,
&get_content_response.content_type.as_deref(), &db.globals,
&get_content_response.file, &get_content_response.content_disposition.as_deref(),
)?; &get_content_response.content_type.as_deref(),
&get_content_response.file,
)
.await?;
Ok(get_content_response.into()) Ok(get_content_response.into())
} else { } else {
@@ -108,22 +119,27 @@ pub async fn get_content_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_content_thumbnail_route( pub async fn get_content_thumbnail_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_content_thumbnail::Request<'_>>, body: Ruma<get_content_thumbnail::Request<'_>>,
) -> ConduitResult<get_content_thumbnail::Response> { ) -> ConduitResult<get_content_thumbnail::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta { if let Some(FileMeta {
content_type, file, .. content_type, file, ..
}) = db.media.get_thumbnail( }) = db
mxc.clone(), .media
body.width .get_thumbnail(
.try_into() mxc.clone(),
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, &db.globals,
body.height body.width
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
)? { body.height
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
)
.await?
{
Ok(get_content_thumbnail::Response { file, content_type }.into()) Ok(get_content_thumbnail::Response { file, content_type }.into())
} else if &*body.server_name != db.globals.server_name() && body.allow_remote { } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
let get_thumbnail_response = db let get_thumbnail_response = db
@@ -142,14 +158,17 @@ pub async fn get_content_thumbnail_route(
) )
.await?; .await?;
db.media.upload_thumbnail( db.media
mxc, .upload_thumbnail(
&None, mxc,
&get_thumbnail_response.content_type, &db.globals,
body.width.try_into().expect("all UInts are valid u32s"), &None,
body.height.try_into().expect("all UInts are valid u32s"), &get_thumbnail_response.content_type,
&get_thumbnail_response.file, body.width.try_into().expect("all UInts are valid u32s"),
)?; body.height.try_into().expect("all UInts are valid u32s"),
&get_thumbnail_response.file,
)
.await?;
Ok(get_thumbnail_response.into()) Ok(get_thumbnail_response.into())
} else { } else {
+87 -85
View File
@@ -4,7 +4,7 @@ use crate::{
pdu::{PduBuilder, PduEvent}, pdu::{PduBuilder, PduEvent},
server_server, utils, ConduitResult, Database, Error, Result, Ruma, server_server, utils, ConduitResult, Database, Error, Result, Ruma,
}; };
use log::{error, warn}; use log::{debug, error, warn};
use member::{MemberEventContent, MembershipState}; use member::{MemberEventContent, MembershipState};
use rocket::futures; use rocket::futures;
use ruma::{ use ruma::{
@@ -25,13 +25,14 @@ use ruma::{
EventType, EventType,
}, },
serde::{to_canonical_value, CanonicalJsonObject, CanonicalJsonValue, Raw}, serde::{to_canonical_value, CanonicalJsonObject, CanonicalJsonValue, Raw},
state_res::{self, RoomVersion},
uint, EventId, RoomId, RoomVersionId, ServerName, UserId, uint, EventId, RoomId, RoomVersionId, ServerName, UserId,
}; };
use state_res::EventMap;
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{btree_map::Entry, BTreeMap, HashSet},
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
time::{Duration, Instant},
}; };
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@@ -43,7 +44,7 @@ use rocket::{get, post};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn join_room_by_id_route( pub async fn join_room_by_id_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<join_room_by_id::Request<'_>>, body: Ruma<join_room_by_id::Request<'_>>,
) -> ConduitResult<join_room_by_id::Response> { ) -> ConduitResult<join_room_by_id::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -80,7 +81,7 @@ pub async fn join_room_by_id_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn join_room_by_id_or_alias_route( pub async fn join_room_by_id_or_alias_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<join_room_by_id_or_alias::Request<'_>>, body: Ruma<join_room_by_id_or_alias::Request<'_>>,
) -> ConduitResult<join_room_by_id_or_alias::Response> { ) -> ConduitResult<join_room_by_id_or_alias::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -134,7 +135,7 @@ pub async fn join_room_by_id_or_alias_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn leave_room_route( pub async fn leave_room_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<leave_room::Request<'_>>, body: Ruma<leave_room::Request<'_>>,
) -> ConduitResult<leave_room::Response> { ) -> ConduitResult<leave_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -152,7 +153,7 @@ pub async fn leave_room_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn invite_user_route( pub async fn invite_user_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<invite_user::Request<'_>>, body: Ruma<invite_user::Request<'_>>,
) -> ConduitResult<invite_user::Response> { ) -> ConduitResult<invite_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -172,7 +173,7 @@ pub async fn invite_user_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn kick_user_route( pub async fn kick_user_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<kick_user::Request<'_>>, body: Ruma<kick_user::Request<'_>>,
) -> ConduitResult<kick_user::Response> { ) -> ConduitResult<kick_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -188,7 +189,8 @@ pub async fn kick_user_route(
ErrorKind::BadState, ErrorKind::BadState,
"Cannot kick member that's not in the room.", "Cannot kick member that's not in the room.",
))? ))?
.content, .content
.clone(),
) )
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
.deserialize() .deserialize()
@@ -221,7 +223,7 @@ pub async fn kick_user_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn ban_user_route( pub async fn ban_user_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<ban_user::Request<'_>>, body: Ruma<ban_user::Request<'_>>,
) -> ConduitResult<ban_user::Response> { ) -> ConduitResult<ban_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -244,11 +246,12 @@ pub async fn ban_user_route(
third_party_invite: None, third_party_invite: None,
}), }),
|event| { |event| {
let mut event = let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
serde_json::from_value::<Raw<member::MemberEventContent>>(event.content) event.content.clone(),
.expect("Raw::from_value always works") )
.deserialize() .expect("Raw::from_value always works")
.map_err(|_| Error::bad_database("Invalid member event in database."))?; .deserialize()
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
event.membership = ruma::events::room::member::MembershipState::Ban; event.membership = ruma::events::room::member::MembershipState::Ban;
Ok(event) Ok(event)
}, },
@@ -278,7 +281,7 @@ pub async fn ban_user_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn unban_user_route( pub async fn unban_user_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<unban_user::Request<'_>>, body: Ruma<unban_user::Request<'_>>,
) -> ConduitResult<unban_user::Response> { ) -> ConduitResult<unban_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -294,7 +297,8 @@ pub async fn unban_user_route(
ErrorKind::BadState, ErrorKind::BadState,
"Cannot unban a user who is not banned.", "Cannot unban a user who is not banned.",
))? ))?
.content, .content
.clone(),
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
.deserialize() .deserialize()
@@ -326,7 +330,7 @@ pub async fn unban_user_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn forget_room_route( pub async fn forget_room_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<forget_room::Request<'_>>, body: Ruma<forget_room::Request<'_>>,
) -> ConduitResult<forget_room::Response> { ) -> ConduitResult<forget_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -344,7 +348,7 @@ pub async fn forget_room_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn joined_rooms_route( pub async fn joined_rooms_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<joined_rooms::Request>, body: Ruma<joined_rooms::Request>,
) -> ConduitResult<joined_rooms::Response> { ) -> ConduitResult<joined_rooms::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -365,7 +369,7 @@ pub async fn joined_rooms_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_member_events_route( pub async fn get_member_events_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_member_events::Request<'_>>, body: Ruma<get_member_events::Request<'_>>,
) -> ConduitResult<get_member_events::Response> { ) -> ConduitResult<get_member_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -395,7 +399,7 @@ pub async fn get_member_events_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn joined_members_route( pub async fn joined_members_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<joined_members::Request<'_>>, body: Ruma<joined_members::Request<'_>>,
) -> ConduitResult<joined_members::Response> { ) -> ConduitResult<joined_members::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -545,12 +549,6 @@ async fn join_room_by_id_helper(
) )
.await?; .await?;
let count = db.globals.next_count()?;
let mut pdu_id = room_id.as_bytes().to_vec();
pdu_id.push(0xff);
pdu_id.extend_from_slice(&count.to_be_bytes());
let pdu = PduEvent::from_id_val(&event_id, join_event.clone()) let pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.map_err(|_| Error::BadServerResponse("Invalid join event PDU."))?; .map_err(|_| Error::BadServerResponse("Invalid join event PDU."))?;
@@ -578,36 +576,6 @@ async fn join_room_by_id_helper(
db.rooms.add_pdu_outlier(&event_id, &value)?; db.rooms.add_pdu_outlier(&event_id, &value)?;
if let Some(state_key) = &pdu.state_key { if let Some(state_key) = &pdu.state_key {
if pdu.kind == EventType::RoomMember {
let target_user_id = UserId::try_from(state_key.clone()).map_err(|e| {
warn!(
"Invalid user id in send_join response: {}: {}",
state_key, e
);
Error::BadServerResponse("Invalid user id in send_join response.")
})?;
let invite_state = Vec::new(); // TODO add a few important events
// Update our membership info, we do this here incase a user is invited
// and immediately leaves we need the DB to record the invite event for auth
db.rooms.update_membership(
&pdu.room_id,
&target_user_id,
serde_json::from_value::<member::MembershipState>(
pdu.content
.get("membership")
.ok_or(Error::BadServerResponse("Invalid member event content"))?
.clone(),
)
.map_err(|_| {
Error::BadServerResponse("Invalid membership state content.")
})?,
&pdu.sender,
Some(invite_state),
db,
)?;
}
state.insert((pdu.kind.clone(), state_key.clone()), pdu.event_id.clone()); state.insert((pdu.kind.clone(), state_key.clone()), pdu.event_id.clone());
} }
} }
@@ -650,8 +618,6 @@ async fn join_room_by_id_helper(
db.rooms.append_pdu( db.rooms.append_pdu(
&pdu, &pdu,
utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"), utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"),
db.globals.next_count()?,
pdu_id.into(),
&[pdu.event_id.clone()], &[pdu.event_id.clone()],
db, db,
)?; )?;
@@ -694,9 +660,41 @@ async fn validate_and_add_event_id(
db: &Database, db: &Database,
) -> Result<(EventId, CanonicalJsonObject)> { ) -> Result<(EventId, CanonicalJsonObject)> {
let mut value = serde_json::from_str::<CanonicalJsonObject>(pdu.json().get()).map_err(|e| { let mut value = serde_json::from_str::<CanonicalJsonObject>(pdu.json().get()).map_err(|e| {
error!("{:?}: {:?}", pdu, e); error!("Invalid PDU in server response: {:?}: {:?}", pdu, e);
Error::BadServerResponse("Invalid PDU in server response") Error::BadServerResponse("Invalid PDU in server response")
})?; })?;
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
let back_off = |id| match db.globals.bad_event_ratelimiter.write().unwrap().entry(id) {
Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
}
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
};
if let Some((time, tries)) = db
.globals
.bad_event_ratelimiter
.read()
.unwrap()
.get(&event_id)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", event_id);
return Err(Error::BadServerResponse("bad event, still backing off"));
}
}
server_server::fetch_required_signing_keys(&value, pub_key_map, db).await?; server_server::fetch_required_signing_keys(&value, pub_key_map, db).await?;
if let Err(e) = ruma::signatures::verify_event( if let Err(e) = ruma::signatures::verify_event(
@@ -706,17 +704,11 @@ async fn validate_and_add_event_id(
&value, &value,
room_version, room_version,
) { ) {
warn!("Event failed verification: {}", e); warn!("Event {} failed verification {:?} {}", event_id, pdu, e);
back_off(event_id);
return Err(Error::BadServerResponse("Event failed verification.")); return Err(Error::BadServerResponse("Event failed verification."));
} }
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
value.insert( value.insert(
"event_id".to_owned(), "event_id".to_owned(),
CanonicalJsonValue::String(event_id.as_str().to_owned()), CanonicalJsonValue::String(event_id.as_str().to_owned()),
@@ -747,27 +739,27 @@ pub async fn invite_helper(
let create_event_content = create_event let create_event_content = create_event
.as_ref() .as_ref()
.map(|create_event| { .map(|create_event| {
Ok::<_, Error>( serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone())
serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone()) .expect("Raw::from_value always works.")
.expect("Raw::from_value always works.") .deserialize()
.deserialize() .map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))
.map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))?,
)
}) })
.transpose()?; .transpose()?;
let create_prev_event = if prev_events.len() == 1 let create_prev_event = if prev_events.len() == 1
&& Some(&prev_events[0]) == create_event.as_ref().map(|c| &c.event_id) && Some(&prev_events[0]) == create_event.as_ref().map(|c| &c.event_id)
{ {
create_event.map(Arc::new) create_event
} else { } else {
None None
}; };
// If there was no create event yet, assume we are creating a version 6 room right now // If there was no create event yet, assume we are creating a version 6 room right now
let room_version = create_event_content.map_or(RoomVersionId::Version6, |create_event| { let room_version_id = create_event_content
create_event.room_version .map_or(RoomVersionId::Version6, |create_event| {
}); create_event.room_version
});
let room_version = RoomVersion::new(&room_version_id).expect("room version is supported");
let content = serde_json::to_value(MemberEventContent { let content = serde_json::to_value(MemberEventContent {
avatar_url: None, avatar_url: None,
@@ -796,10 +788,10 @@ pub async fn invite_helper(
let mut unsigned = BTreeMap::new(); let mut unsigned = BTreeMap::new();
if let Some(prev_pdu) = db.rooms.room_state_get(room_id, &kind, &state_key)? { if let Some(prev_pdu) = db.rooms.room_state_get(room_id, &kind, &state_key)? {
unsigned.insert("prev_content".to_owned(), prev_pdu.content); unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
unsigned.insert( unsigned.insert(
"prev_sender".to_owned(), "prev_sender".to_owned(),
serde_json::to_value(prev_pdu.sender).expect("UserId::to_value always works"), serde_json::to_value(&prev_pdu.sender).expect("UserId::to_value always works"),
); );
} }
@@ -841,7 +833,7 @@ pub async fn invite_helper(
if !auth_check { if !auth_check {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::Forbidden,
"Event is not authorized.", "Event is not authorized.",
)); ));
} }
@@ -863,7 +855,7 @@ pub async fn invite_helper(
db.globals.server_name().as_str(), db.globals.server_name().as_str(),
db.globals.keypair(), db.globals.keypair(),
&mut pdu_json, &mut pdu_json,
&room_version, &room_version_id,
) )
.expect("event is valid, we just created it"); .expect("event is valid, we just created it");
@@ -884,7 +876,6 @@ pub async fn invite_helper(
.await?; .await?;
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
let mut auth_cache = EventMap::new();
// We do not add the event_id field to the pdu here because of signature and hashes checks // We do not add the event_id field to the pdu here because of signature and hashes checks
let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event) { let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event) {
@@ -907,14 +898,24 @@ pub async fn invite_helper(
) )
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?;
let mutex = Arc::clone(
db.globals
.roomid_mutex
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let mutex_lock = mutex.lock().await;
let pdu_id = server_server::handle_incoming_pdu( let pdu_id = server_server::handle_incoming_pdu(
&origin, &origin,
&event_id, &event_id,
&room_id,
value, value,
true, true,
&db, &db,
&pub_key_map, &pub_key_map,
&mut auth_cache,
) )
.await .await
.map_err(|_| { .map_err(|_| {
@@ -927,6 +928,7 @@ pub async fn invite_helper(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Could not accept incoming PDU as timeline event.", "Could not accept incoming PDU as timeline event.",
))?; ))?;
drop(mutex_lock);
for server in db for server in db
.rooms .rooms
@@ -947,7 +949,7 @@ pub async fn invite_helper(
membership: member::MembershipState::Invite, membership: member::MembershipState::Invite,
displayname: db.users.displayname(&user_id)?, displayname: db.users.displayname(&user_id)?,
avatar_url: db.users.avatar_url(&user_id)?, avatar_url: db.users.avatar_url(&user_id)?,
is_direct: None, is_direct: Some(is_direct),
third_party_invite: None, third_party_invite: None,
}) })
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
+5 -9
View File
@@ -11,6 +11,7 @@ use ruma::{
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
sync::Arc,
}; };
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@@ -22,7 +23,7 @@ use rocket::{get, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_message_event_route( pub async fn send_message_event_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<send_message_event::Request<'_>>, body: Ruma<send_message_event::Request<'_>>,
) -> ConduitResult<send_message_event::Response> { ) -> ConduitResult<send_message_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -56,13 +57,8 @@ pub async fn send_message_event_route(
let event_id = db.rooms.build_and_append_pdu( let event_id = db.rooms.build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: EventType::from(&body.event_type), event_type: EventType::from(&body.event_type),
content: serde_json::from_str( content: serde_json::from_str(body.body.body.json().get())
body.json_body .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
.as_ref()
.ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?
.get(),
)
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
unsigned: Some(unsigned), unsigned: Some(unsigned),
state_key: None, state_key: None,
redacts: None, redacts: None,
@@ -90,7 +86,7 @@ pub async fn send_message_event_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_message_events_route( pub async fn get_message_events_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_message_events::Request<'_>>, body: Ruma<get_message_events::Request<'_>>,
) -> ConduitResult<get_message_events::Response> { ) -> ConduitResult<get_message_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+3 -3
View File
@@ -69,9 +69,9 @@ use {
ruma::api::client::r0::to_device::send_event_to_device, ruma::api::client::r0::to_device::send_event_to_device,
}; };
const DEVICE_ID_LENGTH: usize = 10; pub const DEVICE_ID_LENGTH: usize = 10;
const TOKEN_LENGTH: usize = 256; pub const TOKEN_LENGTH: usize = 256;
const SESSION_ID_LENGTH: usize = 256; pub const SESSION_ID_LENGTH: usize = 256;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
#[options("/<_..>")] #[options("/<_..>")]
+49 -4
View File
@@ -1,10 +1,10 @@
use super::State; use super::State;
use crate::{utils, ConduitResult, Database, Ruma}; use crate::{utils, ConduitResult, Database, Ruma};
use ruma::api::client::r0::presence::set_presence; use ruma::api::client::r0::presence::{get_presence, set_presence};
use std::convert::TryInto; use std::{convert::TryInto, sync::Arc, time::Duration};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::put; use rocket::{get, put};
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
@@ -12,7 +12,7 @@ use rocket::put;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_presence_route( pub async fn set_presence_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_presence::Request<'_>>, body: Ruma<set_presence::Request<'_>>,
) -> ConduitResult<set_presence::Response> { ) -> ConduitResult<set_presence::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -46,3 +46,48 @@ pub async fn set_presence_route(
Ok(set_presence::Response.into()) Ok(set_presence::Response.into())
} }
#[cfg_attr(
feature = "conduit_bin",
get("/_matrix/client/r0/presence/<_>/status", data = "<body>")
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_presence_route(
db: State<'_, Arc<Database>>,
body: Ruma<get_presence::Request<'_>>,
) -> ConduitResult<get_presence::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut presence_event = None;
for room_id in db
.rooms
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
{
let room_id = room_id?;
if let Some(presence) = db
.rooms
.edus
.get_last_presence_event(&sender_user, &room_id)?
{
presence_event = Some(presence);
}
}
if let Some(presence) = presence_event {
Ok(get_presence::Response {
// TODO: Should ruma just use the presenceeventcontent type here?
status_msg: presence.content.status_msg,
currently_active: presence.content.currently_active,
last_active_ago: presence
.content
.last_active_ago
.map(|millis| Duration::from_millis(millis.into())),
presence: presence.content.presence,
}
.into())
} else {
todo!();
}
}
+12 -10
View File
@@ -13,7 +13,7 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; use rocket::{get, put};
use std::convert::TryInto; use std::{convert::TryInto, sync::Arc};
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
@@ -21,7 +21,7 @@ use std::convert::TryInto;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_displayname_route( pub async fn set_displayname_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_display_name::Request<'_>>, body: Ruma<set_display_name::Request<'_>>,
) -> ConduitResult<set_display_name::Response> { ) -> ConduitResult<set_display_name::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -49,8 +49,9 @@ pub async fn set_displayname_route(
)? )?
.ok_or_else(|| { .ok_or_else(|| {
Error::bad_database( Error::bad_database(
"Tried to send displayname update for user not in the room.", "Tried to send displayname update for user not in the \
) room.",
)
})? })?
.content .content
.clone(), .clone(),
@@ -107,7 +108,7 @@ pub async fn set_displayname_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_displayname_route( pub async fn get_displayname_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_display_name::Request<'_>>, body: Ruma<get_display_name::Request<'_>>,
) -> ConduitResult<get_display_name::Response> { ) -> ConduitResult<get_display_name::Response> {
Ok(get_display_name::Response { Ok(get_display_name::Response {
@@ -122,7 +123,7 @@ pub async fn get_displayname_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_avatar_url_route( pub async fn set_avatar_url_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_avatar_url::Request<'_>>, body: Ruma<set_avatar_url::Request<'_>>,
) -> ConduitResult<set_avatar_url::Response> { ) -> ConduitResult<set_avatar_url::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -150,8 +151,9 @@ pub async fn set_avatar_url_route(
)? )?
.ok_or_else(|| { .ok_or_else(|| {
Error::bad_database( Error::bad_database(
"Tried to send displayname update for user not in the room.", "Tried to send displayname update for user not in the \
) room.",
)
})? })?
.content .content
.clone(), .clone(),
@@ -208,7 +210,7 @@ pub async fn set_avatar_url_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_avatar_url_route( pub async fn get_avatar_url_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_avatar_url::Request<'_>>, body: Ruma<get_avatar_url::Request<'_>>,
) -> ConduitResult<get_avatar_url::Response> { ) -> ConduitResult<get_avatar_url::Response> {
Ok(get_avatar_url::Response { Ok(get_avatar_url::Response {
@@ -223,7 +225,7 @@ pub async fn get_avatar_url_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_profile_route( pub async fn get_profile_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_profile::Request<'_>>, body: Ruma<get_profile::Request<'_>>,
) -> ConduitResult<get_profile::Response> { ) -> ConduitResult<get_profile::Response> {
if !db.users.exists(&body.user_id)? { if !db.users.exists(&body.user_id)? {
+12 -10
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{ use ruma::{
@@ -22,7 +24,7 @@ use rocket::{delete, get, post, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_pushrules_all_route( pub async fn get_pushrules_all_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_pushrules_all::Request>, body: Ruma<get_pushrules_all::Request>,
) -> ConduitResult<get_pushrules_all::Response> { ) -> ConduitResult<get_pushrules_all::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -47,7 +49,7 @@ pub async fn get_pushrules_all_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_route( pub async fn get_pushrule_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_pushrule::Request<'_>>, body: Ruma<get_pushrule::Request<'_>>,
) -> ConduitResult<get_pushrule::Response> { ) -> ConduitResult<get_pushrule::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -101,7 +103,7 @@ pub async fn get_pushrule_route(
)] )]
#[tracing::instrument(skip(db, req))] #[tracing::instrument(skip(db, req))]
pub async fn set_pushrule_route( pub async fn set_pushrule_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
req: Ruma<set_pushrule::Request<'_>>, req: Ruma<set_pushrule::Request<'_>>,
) -> ConduitResult<set_pushrule::Response> { ) -> ConduitResult<set_pushrule::Response> {
let sender_user = req.sender_user.as_ref().expect("user is authenticated"); let sender_user = req.sender_user.as_ref().expect("user is authenticated");
@@ -204,7 +206,7 @@ pub async fn set_pushrule_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_actions_route( pub async fn get_pushrule_actions_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_pushrule_actions::Request<'_>>, body: Ruma<get_pushrule_actions::Request<'_>>,
) -> ConduitResult<get_pushrule_actions::Response> { ) -> ConduitResult<get_pushrule_actions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -263,7 +265,7 @@ pub async fn get_pushrule_actions_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_pushrule_actions_route( pub async fn set_pushrule_actions_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_pushrule_actions::Request<'_>>, body: Ruma<set_pushrule_actions::Request<'_>>,
) -> ConduitResult<set_pushrule_actions::Response> { ) -> ConduitResult<set_pushrule_actions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -337,7 +339,7 @@ pub async fn set_pushrule_actions_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_enabled_route( pub async fn get_pushrule_enabled_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_pushrule_enabled::Request<'_>>, body: Ruma<get_pushrule_enabled::Request<'_>>,
) -> ConduitResult<get_pushrule_enabled::Response> { ) -> ConduitResult<get_pushrule_enabled::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -398,7 +400,7 @@ pub async fn get_pushrule_enabled_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_pushrule_enabled_route( pub async fn set_pushrule_enabled_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_pushrule_enabled::Request<'_>>, body: Ruma<set_pushrule_enabled::Request<'_>>,
) -> ConduitResult<set_pushrule_enabled::Response> { ) -> ConduitResult<set_pushrule_enabled::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -477,7 +479,7 @@ pub async fn set_pushrule_enabled_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_pushrule_route( pub async fn delete_pushrule_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_pushrule::Request<'_>>, body: Ruma<delete_pushrule::Request<'_>>,
) -> ConduitResult<delete_pushrule::Response> { ) -> ConduitResult<delete_pushrule::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -546,7 +548,7 @@ pub async fn delete_pushrule_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_pushers_route( pub async fn get_pushers_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_pushers::Request>, body: Ruma<get_pushers::Request>,
) -> ConduitResult<get_pushers::Response> { ) -> ConduitResult<get_pushers::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -563,7 +565,7 @@ pub async fn get_pushers_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_pushers_route( pub async fn set_pushers_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_pusher::Request>, body: Ruma<set_pusher::Request>,
) -> ConduitResult<set_pusher::Response> { ) -> ConduitResult<set_pusher::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+25 -31
View File
@@ -5,12 +5,14 @@ use ruma::{
error::ErrorKind, error::ErrorKind,
r0::{read_marker::set_read_marker, receipt::create_receipt}, r0::{read_marker::set_read_marker, receipt::create_receipt},
}, },
events::{AnyEphemeralRoomEvent, AnyEvent, EventType}, events::{AnyEphemeralRoomEvent, EventType},
receipt::ReceiptType,
MilliSecondsSinceUnixEpoch,
}; };
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::post; use rocket::post;
use std::{collections::BTreeMap, time::SystemTime}; use std::{collections::BTreeMap, sync::Arc};
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
@@ -18,7 +20,7 @@ use std::{collections::BTreeMap, time::SystemTime};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn set_read_marker_route( pub async fn set_read_marker_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<set_read_marker::Request<'_>>, body: Ruma<set_read_marker::Request<'_>>,
) -> ConduitResult<set_read_marker::Response> { ) -> ConduitResult<set_read_marker::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -27,7 +29,6 @@ pub async fn set_read_marker_route(
content: ruma::events::fully_read::FullyReadEventContent { content: ruma::events::fully_read::FullyReadEventContent {
event_id: body.fully_read.clone(), event_id: body.fully_read.clone(),
}, },
room_id: body.room_id.clone(),
}; };
db.account_data.update( db.account_data.update(
Some(&body.room_id), Some(&body.room_id),
@@ -54,26 +55,23 @@ pub async fn set_read_marker_route(
user_receipts.insert( user_receipts.insert(
sender_user.clone(), sender_user.clone(),
ruma::events::receipt::Receipt { ruma::events::receipt::Receipt {
ts: Some(SystemTime::now()), ts: Some(MilliSecondsSinceUnixEpoch::now()),
}, },
); );
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new(); let mut receipt_content = BTreeMap::new();
receipt_content.insert( receipt_content.insert(event.to_owned(), receipts);
event.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
db.rooms.edus.readreceipt_update( db.rooms.edus.readreceipt_update(
&sender_user, &sender_user,
&body.room_id, &body.room_id,
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt( AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
ruma::events::receipt::ReceiptEvent { content: ruma::events::receipt::ReceiptEventContent(receipt_content),
content: ruma::events::receipt::ReceiptEventContent(receipt_content), room_id: body.room_id.clone(),
room_id: body.room_id.clone(), }),
},
)),
&db.globals, &db.globals,
)?; )?;
} }
@@ -89,7 +87,7 @@ pub async fn set_read_marker_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn create_receipt_route( pub async fn create_receipt_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_receipt::Request<'_>>, body: Ruma<create_receipt::Request<'_>>,
) -> ConduitResult<create_receipt::Response> { ) -> ConduitResult<create_receipt::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -112,26 +110,22 @@ pub async fn create_receipt_route(
user_receipts.insert( user_receipts.insert(
sender_user.clone(), sender_user.clone(),
ruma::events::receipt::Receipt { ruma::events::receipt::Receipt {
ts: Some(SystemTime::now()), ts: Some(MilliSecondsSinceUnixEpoch::now()),
}, },
); );
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new(); let mut receipt_content = BTreeMap::new();
receipt_content.insert( receipt_content.insert(body.event_id.to_owned(), receipts);
body.event_id.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
db.rooms.edus.readreceipt_update( db.rooms.edus.readreceipt_update(
&sender_user, &sender_user,
&body.room_id, &body.room_id,
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt( AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
ruma::events::receipt::ReceiptEvent { content: ruma::events::receipt::ReceiptEventContent(receipt_content),
content: ruma::events::receipt::ReceiptEventContent(receipt_content), room_id: body.room_id.clone(),
room_id: body.room_id.clone(), }),
},
)),
&db.globals, &db.globals,
)?; )?;
+2 -1
View File
@@ -4,6 +4,7 @@ use ruma::{
api::client::r0::redact::redact_event, api::client::r0::redact::redact_event,
events::{room::redaction, EventType}, events::{room::redaction, EventType},
}; };
use std::sync::Arc;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::put; use rocket::put;
@@ -14,7 +15,7 @@ use rocket::put;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn redact_event_route( pub async fn redact_event_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<redact_event::Request<'_>>, body: Ruma<redact_event::Request<'_>>,
) -> ConduitResult<redact_event::Response> { ) -> ConduitResult<redact_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+26 -26
View File
@@ -1,6 +1,5 @@
use super::State; use super::State;
use crate::client_server::invite_helper; use crate::{client_server::invite_helper, pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
use log::info; use log::info;
use ruma::{ use ruma::{
api::client::{ api::client::{
@@ -14,7 +13,7 @@ use ruma::{
serde::Raw, serde::Raw,
RoomAliasId, RoomId, RoomVersionId, RoomAliasId, RoomId, RoomVersionId,
}; };
use std::{cmp::max, collections::BTreeMap, convert::TryFrom}; use std::{cmp::max, collections::BTreeMap, convert::TryFrom, sync::Arc};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; use rocket::{get, post};
@@ -25,7 +24,7 @@ use rocket::{get, post};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn create_room_route( pub async fn create_room_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_room::Request<'_>>, body: Ruma<create_room::Request<'_>>,
) -> ConduitResult<create_room::Response> { ) -> ConduitResult<create_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -112,27 +111,26 @@ pub async fn create_room_route(
} }
} }
let power_levels_content = if let Some(power_levels) = &body.power_level_content_override { let mut power_levels_content =
serde_json::from_str(power_levels.json().get()).map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
})?
} else {
serde_json::to_value(ruma::events::room::power_levels::PowerLevelsEventContent { serde_json::to_value(ruma::events::room::power_levels::PowerLevelsEventContent {
ban: 50.into(),
events: BTreeMap::new(),
events_default: 0.into(),
invite: 50.into(),
kick: 50.into(),
redact: 50.into(),
state_default: 50.into(),
users, users,
users_default: 0.into(), ..Default::default()
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
room: 50.into(),
},
}) })
.expect("event is valid, we just created it") .expect("event is valid, we just created it");
};
if let Some(power_level_content_override) = &body.power_level_content_override {
let json = serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(
power_level_content_override.json().get(),
)
.map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
})?;
for (key, value) in json {
power_levels_content[key] = value;
}
}
db.rooms.build_and_append_pdu( db.rooms.build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: EventType::RoomPowerLevels, event_type: EventType::RoomPowerLevels,
@@ -296,7 +294,7 @@ pub async fn create_room_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_room_event_route( pub async fn get_room_event_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_room_event::Request<'_>>, body: Ruma<get_room_event::Request<'_>>,
) -> ConduitResult<get_room_event::Response> { ) -> ConduitResult<get_room_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -324,7 +322,7 @@ pub async fn get_room_event_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn upgrade_room_route( pub async fn upgrade_room_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<upgrade_room::Request<'_>>, body: Ruma<upgrade_room::Request<'_>>,
_room_id: String, _room_id: String,
) -> ConduitResult<upgrade_room::Response> { ) -> ConduitResult<upgrade_room::Response> {
@@ -364,7 +362,8 @@ pub async fn upgrade_room_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomCreate, "")? .room_state_get(&body.room_id, &EventType::RoomCreate, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.content, .content
.clone(),
) )
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
.deserialize() .deserialize()
@@ -465,7 +464,8 @@ pub async fn upgrade_room_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")? .room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.content, .content
.clone(),
) )
.expect("database contains invalid PDU") .expect("database contains invalid PDU")
.deserialize() .deserialize()
+42 -14
View File
@@ -1,6 +1,7 @@
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{error::ErrorKind, r0::search::search_events}; use ruma::api::client::{error::ErrorKind, r0::search::search_events};
use std::sync::Arc;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::post; use rocket::post;
@@ -13,23 +14,38 @@ use std::collections::BTreeMap;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn search_events_route( pub async fn search_events_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<search_events::Request<'_>>, body: Ruma<search_events::Request<'_>>,
) -> ConduitResult<search_events::Response> { ) -> ConduitResult<search_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let search_criteria = body.search_categories.room_events.as_ref().unwrap(); let search_criteria = body.search_categories.room_events.as_ref().unwrap();
let filter = search_criteria.filter.as_ref().unwrap(); let filter = search_criteria.filter.clone().unwrap_or_default();
let room_id = filter.rooms.as_ref().unwrap().first().unwrap(); let room_ids = filter.rooms.clone().unwrap_or_else(|| {
db.rooms
.rooms_joined(&sender_user)
.filter_map(|r| r.ok())
.collect()
});
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize); let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
if !db.rooms.is_joined(sender_user, &room_id)? { let mut searches = Vec::new();
return Err(Error::BadRequest(
ErrorKind::Forbidden, for room_id in room_ids {
"You don't have permission to view this room.", if !db.rooms.is_joined(sender_user, &room_id)? {
)); return Err(Error::BadRequest(
ErrorKind::Forbidden,
"You don't have permission to view this room.",
));
}
let search = db
.rooms
.search_pdus(&room_id, &search_criteria.search_term)?;
searches.push(search.0.peekable());
} }
let skip = match body.next_batch.as_ref().map(|s| s.parse()) { let skip = match body.next_batch.as_ref().map(|s| s.parse()) {
@@ -43,12 +59,20 @@ pub async fn search_events_route(
None => 0, // Default to the start None => 0, // Default to the start
}; };
let search = db let mut results = Vec::new();
.rooms for _ in 0..skip + limit {
.search_pdus(&room_id, &search_criteria.search_term)?; if let Some(s) = searches
.iter_mut()
.map(|s| (s.peek().cloned(), s))
.max_by_key(|(peek, _)| peek.clone())
.and_then(|(_, i)| i.next())
{
results.push(s);
}
}
let results = search let results = results
.0 .iter()
.map(|result| { .map(|result| {
Ok::<_, Error>(SearchResult { Ok::<_, Error>(SearchResult {
context: EventContextResult { context: EventContextResult {
@@ -83,7 +107,11 @@ pub async fn search_events_route(
next_batch, next_batch,
results, results,
state: BTreeMap::new(), // TODO state: BTreeMap::new(), // TODO
highlights: search.1, highlights: search_criteria
.search_term
.split_terminator(|c: char| !c.is_alphanumeric())
.map(str::to_lowercase)
.collect::<Vec<_>>(),
}, },
}) })
.into()) .into())
+7 -7
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH}; use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Ruma}; use crate::{utils, ConduitResult, Database, Error, Ruma};
use log::info; use log::info;
@@ -50,7 +52,7 @@ pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response>
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn login_route( pub async fn login_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<login::Request<'_>>, body: Ruma<login::Request<'_>>,
) -> ConduitResult<login::Response> { ) -> ConduitResult<login::Response> {
// Validate login method // Validate login method
@@ -167,7 +169,7 @@ pub async fn login_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn logout_route( pub async fn logout_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<logout::Request>, body: Ruma<logout::Request>,
) -> ConduitResult<logout::Response> { ) -> ConduitResult<logout::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -195,15 +197,13 @@ pub async fn logout_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn logout_all_route( pub async fn logout_all_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<logout_all::Request>, body: Ruma<logout_all::Request>,
) -> ConduitResult<logout_all::Response> { ) -> ConduitResult<logout_all::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for device_id in db.users.all_device_ids(sender_user) { for device_id in db.users.all_device_ids(sender_user).flatten() {
if let Ok(device_id) = device_id { db.users.remove_device(&sender_user, &device_id)?;
db.users.remove_device(&sender_user, &device_id)?;
}
} }
db.flush().await?; db.flush().await?;
+12 -10
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma}; use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma};
use ruma::{ use ruma::{
@@ -25,7 +27,7 @@ use rocket::{get, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_key_route( pub async fn send_state_event_for_key_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<send_state_event::Request<'_>>, body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event::Response> { ) -> ConduitResult<send_state_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -51,7 +53,7 @@ pub async fn send_state_event_for_key_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_empty_key_route( pub async fn send_state_event_for_empty_key_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<send_state_event::Request<'_>>, body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event::Response> { ) -> ConduitResult<send_state_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -77,7 +79,7 @@ pub async fn send_state_event_for_empty_key_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_state_events_route( pub async fn get_state_events_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_state_events::Request<'_>>, body: Ruma<get_state_events::Request<'_>>,
) -> ConduitResult<get_state_events::Response> { ) -> ConduitResult<get_state_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -90,7 +92,7 @@ pub async fn get_state_events_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
"Invalid room history visibility event in database.", "Invalid room history visibility event in database.",
@@ -124,7 +126,7 @@ pub async fn get_state_events_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_state_events_for_key_route( pub async fn get_state_events_for_key_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_state_events_for_key::Request<'_>>, body: Ruma<get_state_events_for_key::Request<'_>>,
) -> ConduitResult<get_state_events_for_key::Response> { ) -> ConduitResult<get_state_events_for_key::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -137,7 +139,7 @@ pub async fn get_state_events_for_key_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
"Invalid room history visibility event in database.", "Invalid room history visibility event in database.",
@@ -163,7 +165,7 @@ pub async fn get_state_events_for_key_route(
))?; ))?;
Ok(get_state_events_for_key::Response { Ok(get_state_events_for_key::Response {
content: serde_json::from_value(event.content) content: serde_json::from_value(event.content.clone())
.map_err(|_| Error::bad_database("Invalid event content in database"))?, .map_err(|_| Error::bad_database("Invalid event content in database"))?,
} }
.into()) .into())
@@ -175,7 +177,7 @@ pub async fn get_state_events_for_key_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_state_events_for_empty_key_route( pub async fn get_state_events_for_empty_key_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_state_events_for_key::Request<'_>>, body: Ruma<get_state_events_for_key::Request<'_>>,
) -> ConduitResult<get_state_events_for_key::Response> { ) -> ConduitResult<get_state_events_for_key::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -188,7 +190,7 @@ pub async fn get_state_events_for_empty_key_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
"Invalid room history visibility event in database.", "Invalid room history visibility event in database.",
@@ -214,7 +216,7 @@ pub async fn get_state_events_for_empty_key_route(
))?; ))?;
Ok(get_state_events_for_key::Response { Ok(get_state_events_for_key::Response {
content: serde_json::from_value(event.content) content: serde_json::from_value(event.content.clone())
.map_err(|_| Error::bad_database("Invalid event content in database"))?, .map_err(|_| Error::bad_database("Invalid event content in database"))?,
} }
.into()) .into())
+375 -228
View File
@@ -1,19 +1,22 @@
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Result, Ruma, RumaResponse};
use log::error;
use ruma::{ use ruma::{
api::client::r0::sync::sync_events, api::client::r0::{sync::sync_events, uiaa::UiaaResponse},
events::{room::member::MembershipState, AnySyncEphemeralRoomEvent, EventType}, events::{room::member::MembershipState, AnySyncEphemeralRoomEvent, EventType},
serde::Raw, serde::Raw,
RoomId, UserId, DeviceId, RoomId, UserId,
}; };
use std::{
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet},
convert::{TryFrom, TryInto},
sync::Arc,
time::Duration,
};
use tokio::sync::watch::Sender;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, tokio}; use rocket::{get, tokio};
use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet},
convert::{TryFrom, TryInto},
time::Duration,
};
/// # `GET /_matrix/client/r0/sync` /// # `GET /_matrix/client/r0/sync`
/// ///
@@ -32,23 +35,138 @@ use std::{
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn sync_events_route( pub async fn sync_events_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<sync_events::Request<'_>>, body: Ruma<sync_events::Request<'_>>,
) -> ConduitResult<sync_events::Response> { ) -> std::result::Result<RumaResponse<sync_events::Response>, RumaResponse<UiaaResponse>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated"); let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut rx = match db
.globals
.sync_receivers
.write()
.unwrap()
.entry((sender_user.clone(), sender_device.clone()))
{
Entry::Vacant(v) => {
let (tx, rx) = tokio::sync::watch::channel(None);
tokio::spawn(sync_helper_wrapper(
Arc::clone(&db),
sender_user.clone(),
sender_device.clone(),
body.since.clone(),
body.full_state,
body.timeout,
tx,
));
v.insert((body.since.clone(), rx)).1.clone()
}
Entry::Occupied(mut o) => {
if o.get().0 != body.since {
let (tx, rx) = tokio::sync::watch::channel(None);
tokio::spawn(sync_helper_wrapper(
Arc::clone(&db),
sender_user.clone(),
sender_device.clone(),
body.since.clone(),
body.full_state,
body.timeout,
tx,
));
o.insert((body.since.clone(), rx.clone()));
rx
} else {
o.get().1.clone()
}
}
};
let we_have_to_wait = rx.borrow().is_none();
if we_have_to_wait {
if let Err(e) = rx.changed().await {
error!("Error waiting for sync: {}", e);
}
}
let result = match rx
.borrow()
.as_ref()
.expect("When sync channel changes it's always set to some")
{
Ok(response) => Ok(response.clone()),
Err(error) => Err(error.to_response()),
};
result
}
pub async fn sync_helper_wrapper(
db: Arc<Database>,
sender_user: UserId,
sender_device: Box<DeviceId>,
since: Option<String>,
full_state: bool,
timeout: Option<Duration>,
tx: Sender<Option<ConduitResult<sync_events::Response>>>,
) {
let r = sync_helper(
Arc::clone(&db),
sender_user.clone(),
sender_device.clone(),
since.clone(),
full_state,
timeout,
)
.await;
if let Ok((_, caching_allowed)) = r {
if !caching_allowed {
match db
.globals
.sync_receivers
.write()
.unwrap()
.entry((sender_user, sender_device))
{
Entry::Occupied(o) => {
// Only remove if the device didn't start a different /sync already
if o.get().0 == since {
o.remove();
}
}
Entry::Vacant(_) => {}
}
}
}
let _ = tx.send(Some(r.map(|(r, _)| r.into())));
}
async fn sync_helper(
db: Arc<Database>,
sender_user: UserId,
sender_device: Box<DeviceId>,
since: Option<String>,
full_state: bool,
timeout: Option<Duration>,
// bool = caching allowed
) -> std::result::Result<(sync_events::Response, bool), Error> {
// TODO: match body.set_presence { // TODO: match body.set_presence {
db.rooms.edus.ping_presence(&sender_user)?; db.rooms.edus.ping_presence(&sender_user)?;
// Setup watchers, so if there's no response, we can wait for them // Setup watchers, so if there's no response, we can wait for them
let watcher = db.watch(sender_user, sender_device); let watcher = db.watch(&sender_user, &sender_device);
let next_batch = db.globals.current_count()?.to_string(); let next_batch = db.globals.current_count()?;
let next_batch_string = next_batch.to_string();
let mut joined_rooms = BTreeMap::new(); let mut joined_rooms = BTreeMap::new();
let since = body let since = since
.since
.clone() .clone()
.and_then(|string| string.parse().ok()) .and_then(|string| string.parse().ok())
.unwrap_or(0); .unwrap_or(0);
@@ -70,13 +188,23 @@ pub async fn sync_events_route(
let mut non_timeline_pdus = db let mut non_timeline_pdus = db
.rooms .rooms
.pdus_since(&sender_user, &room_id, since)? .pdus_until(&sender_user, &room_id, u64::MAX)
.filter_map(|r| r.ok()); // Filter out buggy events .filter_map(|r| {
// Filter out buggy events
if r.is_err() {
error!("Bad pdu in pdus_since: {:?}", r);
}
r.ok()
})
.take_while(|(pduid, _)| {
db.rooms
.pdu_count(pduid)
.map_or(false, |count| count > since)
});
// Take the last 10 events for the timeline // Take the last 10 events for the timeline
let timeline_pdus = non_timeline_pdus let timeline_pdus = non_timeline_pdus
.by_ref() .by_ref()
.rev()
.take(10) .take(10)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter() .into_iter()
@@ -96,24 +224,95 @@ pub async fn sync_events_route(
// Database queries: // Database queries:
let current_shortstatehash = db.rooms.current_shortstatehash(&room_id)?; let current_shortstatehash = db
.rooms
.current_shortstatehash(&room_id)?
.expect("All rooms have state");
let first_pdu_before_since = db
.rooms
.pdus_until(&sender_user, &room_id, since)
.next()
.transpose()?;
// These type is Option<Option<_>>. The outer Option is None when there is no event between
// since and the current room state, meaning there should be no updates.
// The inner Option is None when there is an event, but there is no state hash associated
// with it. This can happen for the RoomCreate event, so all updates should arrive.
let first_pdu_before_since = db.rooms.pdus_until(sender_user, &room_id, since).next();
let pdus_after_since = db let pdus_after_since = db
.rooms .rooms
.pdus_after(sender_user, &room_id, since) .pdus_after(&sender_user, &room_id, since)
.next() .next()
.is_some(); .is_some();
let since_shortstatehash = first_pdu_before_since.as_ref().map(|pdu| { let since_shortstatehash = first_pdu_before_since
db.rooms .as_ref()
.pdu_shortstatehash(&pdu.as_ref().ok()?.1.event_id) .map(|pdu| {
.ok()? db.rooms
}); .pdu_shortstatehash(&pdu.1.event_id)
.transpose()
.expect("all pdus have state")
})
.transpose()?;
// Calculates joined_member_count, invited_member_count and heroes
let calculate_counts = || {
let joined_member_count = db.rooms.room_members(&room_id).count();
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
// Recalculate heroes (first 5 members)
let mut heroes = Vec::new();
if joined_member_count + invited_member_count <= 5 {
// 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 db
.rooms
.all_pdus(&sender_user, &room_id)
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
.map(|(_, pdu)| {
let content = serde_json::from_value::<
ruma::events::room::member::MemberEventContent,
>(pdu.content.clone())
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
if let Some(state_key) = &pdu.state_key {
let user_id = UserId::try_from(state_key.clone()).map_err(|_| {
Error::bad_database("Invalid UserId in member PDU.")
})?;
// The membership was and still is invite or join
if matches!(
content.membership,
MembershipState::Join | MembershipState::Invite
) && (db.rooms.is_joined(&user_id, &room_id)?
|| db.rooms.is_invited(&user_id, &room_id)?)
{
Ok::<_, Error>(Some(state_key.clone()))
} else {
Ok(None)
}
} else {
Ok(None)
}
})
// Filter out buggy users
.filter_map(|u| u.ok())
// Filter for possible heroes
.flatten()
{
if heroes.contains(&hero) || hero == sender_user.as_str() {
continue;
}
heroes.push(hero);
}
}
(
Some(joined_member_count),
Some(invited_member_count),
heroes,
)
};
let ( let (
heroes, heroes,
@@ -121,65 +320,107 @@ pub async fn sync_events_route(
invited_member_count, invited_member_count,
joined_since_last_sync, joined_since_last_sync,
state_events, state_events,
) = if pdus_after_since && Some(current_shortstatehash) != since_shortstatehash { ) = if since_shortstatehash.is_none() {
let current_state = db.rooms.room_state_full(&room_id)?; // Probably since = 0, we will do an initial sync
let current_members = current_state let (joined_member_count, invited_member_count, heroes) = calculate_counts();
.iter()
.filter(|(key, _)| key.0 == EventType::RoomMember)
.map(|(key, value)| (&key.1, value)) // Only keep state key
.collect::<Vec<_>>();
let encrypted_room = current_state
.get(&(EventType::RoomEncryption, "".to_owned()))
.is_some();
let since_state = since_shortstatehash
.as_ref()
.map(|since_shortstatehash| {
Ok::<_, Error>(
since_shortstatehash
.map(|since_shortstatehash| db.rooms.state_full(since_shortstatehash))
.transpose()?,
)
})
.transpose()?;
let since_encryption = since_state.as_ref().map(|state| { let current_state_ids = db.rooms.state_full_ids(current_shortstatehash)?;
state let state_events = current_state_ids
.as_ref() .iter()
.map(|state| state.get(&(EventType::RoomEncryption, "".to_owned()))) .map(|id| db.rooms.get_pdu(id))
}); .filter_map(|r| r.ok().flatten())
.collect::<Vec<_>>();
(
heroes,
joined_member_count,
invited_member_count,
true,
state_events
)
} else if !pdus_after_since || since_shortstatehash == Some(current_shortstatehash) {
// No state changes
(Vec::new(), None, None, false, Vec::new())
} else {
// Incremental /sync
let since_shortstatehash = since_shortstatehash.unwrap();
let since_sender_member = db
.rooms
.state_get(
since_shortstatehash,
&EventType::RoomMember,
sender_user.as_str(),
)?
.and_then(|pdu| {
serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
pdu.content.clone(),
)
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PDU in database."))
.ok()
});
let joined_since_last_sync = since_sender_member
.map_or(true, |member| member.membership != MembershipState::Join);
let current_state_ids = db.rooms.state_full_ids(current_shortstatehash)?;
let since_state_ids = db.rooms.state_full_ids(since_shortstatehash)?;
let state_events = if joined_since_last_sync {
current_state_ids
.iter()
.map(|id| db.rooms.get_pdu(id))
.filter_map(|r| r.ok().flatten())
.collect::<Vec<_>>()
} else {
current_state_ids
.difference(&since_state_ids)
.filter(|id| {
!timeline_pdus
.iter()
.any(|(_, timeline_pdu)| timeline_pdu.event_id == **id)
})
.map(|id| db.rooms.get_pdu(id))
.filter_map(|r| r.ok().flatten())
.collect()
};
let encrypted_room = db
.rooms
.state_get(current_shortstatehash, &EventType::RoomEncryption, "")?
.is_some();
let since_encryption =
db.rooms
.state_get(since_shortstatehash, &EventType::RoomEncryption, "")?;
// Calculations: // Calculations:
let new_encrypted_room = let new_encrypted_room = encrypted_room && since_encryption.is_none();
encrypted_room && since_encryption.map_or(true, |encryption| encryption.is_none());
let send_member_count = since_state.as_ref().map_or(true, |since_state| { let send_member_count = state_events
since_state.as_ref().map_or(true, |since_state| { .iter()
current_members.len() .any(|event| event.kind == EventType::RoomMember);
!= since_state
.iter()
.filter(|(key, _)| key.0 == EventType::RoomMember)
.count()
})
});
let since_sender_member = since_state.as_ref().map(|since_state| {
since_state.as_ref().and_then(|state| {
state
.get(&(EventType::RoomMember, sender_user.as_str().to_owned()))
.and_then(|pdu| {
serde_json::from_value::<
Raw<ruma::events::room::member::MemberEventContent>,
>(pdu.content.clone())
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PDU in database."))
.ok()
})
})
});
if encrypted_room { if encrypted_room {
for (user_id, current_member) in current_members { for (user_id, current_member) in db
.rooms
.room_members(&room_id)
.filter_map(|r| r.ok())
.filter_map(|user_id| {
db.rooms
.state_get(
current_shortstatehash,
&EventType::RoomMember,
user_id.as_str(),
)
.ok()
.flatten()
.map(|current_member| (user_id, current_member))
})
{
let current_membership = serde_json::from_value::< let current_membership = serde_json::from_value::<
Raw<ruma::events::room::member::MemberEventContent>, Raw<ruma::events::room::member::MemberEventContent>,
>(current_member.content.clone()) >(current_member.content.clone())
@@ -188,31 +429,23 @@ pub async fn sync_events_route(
.map_err(|_| Error::bad_database("Invalid PDU in database."))? .map_err(|_| Error::bad_database("Invalid PDU in database."))?
.membership; .membership;
let since_membership = let since_membership = db
since_state .rooms
.as_ref() .state_get(
.map_or(MembershipState::Leave, |since_state| { since_shortstatehash,
since_state &EventType::RoomMember,
.as_ref() user_id.as_str(),
.and_then(|since_state| { )?
since_state .and_then(|since_member| {
.get(&(EventType::RoomMember, user_id.clone())) serde_json::from_value::<
.and_then(|since_member| { Raw<ruma::events::room::member::MemberEventContent>,
serde_json::from_value::< >(since_member.content.clone())
Raw<ruma::events::room::member::MemberEventContent>, .expect("Raw::from_value always works")
>( .deserialize()
since_member.content.clone() .map_err(|_| Error::bad_database("Invalid PDU in database."))
) .ok()
.expect("Raw::from_value always works") })
.deserialize() .map_or(MembershipState::Leave, |member| member.membership);
.map_err(|_| {
Error::bad_database("Invalid PDU in database.")
})
.ok()
})
})
.map_or(MembershipState::Leave, |member| member.membership)
});
let user_id = UserId::try_from(user_id.clone()) let user_id = UserId::try_from(user_id.clone())
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?; .map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
@@ -220,10 +453,11 @@ pub async fn sync_events_route(
match (since_membership, current_membership) { match (since_membership, current_membership) {
(MembershipState::Leave, MembershipState::Join) => { (MembershipState::Leave, MembershipState::Join) => {
// A new user joined an encrypted room // A new user joined an encrypted room
if !share_encrypted_room(&db, &sender_user, &user_id, &room_id) { if !share_encrypted_room(&db, &sender_user, &user_id, &room_id)? {
device_list_updates.insert(user_id); device_list_updates.insert(user_id);
} }
} }
// TODO: Remove, this should never happen here, right?
(MembershipState::Join, MembershipState::Leave) => { (MembershipState::Join, MembershipState::Leave) => {
// Write down users that have left encrypted rooms we are in // Write down users that have left encrypted rooms we are in
left_encrypted_users.insert(user_id); left_encrypted_users.insert(user_id);
@@ -233,123 +467,30 @@ pub async fn sync_events_route(
} }
} }
let joined_since_last_sync = since_sender_member.map_or(true, |member| {
member.map_or(true, |member| member.membership != MembershipState::Join)
});
if joined_since_last_sync && encrypted_room || new_encrypted_room { if joined_since_last_sync && encrypted_room || new_encrypted_room {
// If the user is in a new encrypted room, give them all joined users // If the user is in a new encrypted room, give them all joined users
device_list_updates.extend( device_list_updates.extend(
db.rooms db.rooms
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user_id| Some(user_id.ok()?)) .flatten()
.filter(|user_id| { .filter(|user_id| {
// Don't send key updates from the sender to the sender // Don't send key updates from the sender to the sender
sender_user != user_id &sender_user != user_id
}) })
.filter(|user_id| { .filter(|user_id| {
// Only send keys if the sender doesn't share an encrypted room with the target already // Only send keys if the sender doesn't share an encrypted room with the target already
!share_encrypted_room(&db, sender_user, user_id, &room_id) !share_encrypted_room(&db, &sender_user, user_id, &room_id)
.unwrap_or(false)
}), }),
); );
} }
let (joined_member_count, invited_member_count, heroes) = if send_member_count { let (joined_member_count, invited_member_count, heroes) = if send_member_count {
let joined_member_count = db.rooms.room_members(&room_id).count(); calculate_counts()
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
// Recalculate heroes (first 5 members)
let mut heroes = Vec::new();
if joined_member_count + invited_member_count <= 5 {
// 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 db
.rooms
.all_pdus(&sender_user, &room_id)?
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
.map(|(_, pdu)| {
let content = serde_json::from_value::<
Raw<ruma::events::room::member::MemberEventContent>,
>(pdu.content.clone())
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| {
Error::bad_database("Invalid member event in database.")
})?;
if let Some(state_key) = &pdu.state_key {
let user_id =
UserId::try_from(state_key.clone()).map_err(|_| {
Error::bad_database("Invalid UserId in member PDU.")
})?;
// The membership was and still is invite or join
if matches!(
content.membership,
MembershipState::Join | MembershipState::Invite
) && (db.rooms.is_joined(&user_id, &room_id)?
|| db.rooms.is_invited(&user_id, &room_id)?)
{
Ok::<_, Error>(Some(state_key.clone()))
} else {
Ok(None)
}
} else {
Ok(None)
}
})
.filter_map(|u| u.ok()) // Filter out buggy users
// Filter for possible heroes
.filter_map(|u| u)
{
if heroes.contains(&hero) || hero == sender_user.as_str() {
continue;
}
heroes.push(hero);
}
}
(
Some(joined_member_count),
Some(invited_member_count),
heroes,
)
} else { } else {
(None, None, Vec::new()) (None, None, Vec::new())
}; };
let state_events = if joined_since_last_sync {
current_state
.into_iter()
.map(|(_, pdu)| pdu.to_sync_state_event())
.collect()
} else {
match since_state {
None => Vec::new(),
Some(Some(since_state)) => current_state
.iter()
.filter(|(key, value)| {
since_state.get(key).map(|e| &e.event_id) != Some(&value.event_id)
})
.filter(|(_, value)| {
!timeline_pdus.iter().any(|(_, timeline_pdu)| {
timeline_pdu.kind == value.kind
&& timeline_pdu.state_key == value.state_key
})
})
.map(|(_, pdu)| pdu.to_sync_state_event())
.collect(),
Some(None) => current_state
.iter()
.map(|(_, pdu)| pdu.to_sync_state_event())
.collect(),
}
};
( (
heroes, heroes,
joined_member_count, joined_member_count,
@@ -357,8 +498,6 @@ pub async fn sync_events_route(
joined_since_last_sync, joined_since_last_sync,
state_events, state_events,
) )
} else {
(Vec::new(), None, None, false, Vec::new())
}; };
// Look for device list updates in this room // Look for device list updates in this room
@@ -404,8 +543,9 @@ pub async fn sync_events_route(
let mut edus = db let mut edus = db
.rooms .rooms
.edus .edus
.readreceipts_since(&room_id, since)? .readreceipts_since(&room_id, since)
.filter_map(|r| r.ok()) // Filter out buggy events .filter_map(|r| r.ok()) // Filter out buggy events
.map(|(_, _, v)| v)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if db.rooms.edus.last_typing_update(&room_id, &db.globals)? > since { if db.rooms.edus.last_typing_update(&room_id, &db.globals)? > since {
@@ -421,7 +561,7 @@ pub async fn sync_events_route(
} }
let joined_room = sync_events::JoinedRoom { let joined_room = sync_events::JoinedRoom {
account_data: sync_events::AccountData { account_data: sync_events::RoomAccountData {
events: db events: db
.account_data .account_data
.changes_since(Some(&room_id), &sender_user, since)? .changes_since(Some(&room_id), &sender_user, since)?
@@ -448,7 +588,10 @@ pub async fn sync_events_route(
events: room_events, events: room_events,
}, },
state: sync_events::State { state: sync_events::State {
events: state_events, events: state_events
.iter()
.map(|pdu| pdu.to_sync_state_event())
.collect(),
}, },
ephemeral: sync_events::Ephemeral { events: edus }, ephemeral: sync_events::Ephemeral { events: edus },
}; };
@@ -505,10 +648,10 @@ pub async fn sync_events_route(
left_rooms.insert( left_rooms.insert(
room_id.clone(), room_id.clone(),
sync_events::LeftRoom { sync_events::LeftRoom {
account_data: sync_events::AccountData { events: Vec::new() }, account_data: sync_events::RoomAccountData { events: Vec::new() },
timeline: sync_events::Timeline { timeline: sync_events::Timeline {
limited: false, limited: false,
prev_batch: Some(next_batch.clone()), prev_batch: Some(next_batch_string.clone()),
events: Vec::new(), events: Vec::new(),
}, },
state: sync_events::State { state: sync_events::State {
@@ -541,7 +684,7 @@ pub async fn sync_events_route(
for user_id in left_encrypted_users { for user_id in left_encrypted_users {
let still_share_encrypted_room = db let still_share_encrypted_room = db
.rooms .rooms
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()]) .get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.filter_map(|other_room_id| { .filter_map(|other_room_id| {
Some( Some(
@@ -561,10 +704,10 @@ pub async fn sync_events_route(
// Remove all to-device events the device received *last time* // Remove all to-device events the device received *last time*
db.users db.users
.remove_to_device_events(sender_user, sender_device, since)?; .remove_to_device_events(&sender_user, &sender_device, since)?;
let response = sync_events::Response { let response = sync_events::Response {
next_batch, next_batch: next_batch_string,
rooms: sync_events::Rooms { rooms: sync_events::Rooms {
leave: left_rooms, leave: left_rooms,
join: joined_rooms, join: joined_rooms,
@@ -576,7 +719,7 @@ pub async fn sync_events_route(
.map(|(_, v)| Raw::from(v)) .map(|(_, v)| Raw::from(v))
.collect(), .collect(),
}, },
account_data: sync_events::AccountData { account_data: sync_events::GlobalAccountData {
events: db events: db
.account_data .account_data
.changes_since(None, &sender_user, since)? .changes_since(None, &sender_user, since)?
@@ -592,20 +735,22 @@ pub async fn sync_events_route(
changed: device_list_updates.into_iter().collect(), changed: device_list_updates.into_iter().collect(),
left: device_list_left.into_iter().collect(), left: device_list_left.into_iter().collect(),
}, },
device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_user)? > since device_one_time_keys_count: if db.users.last_one_time_keys_update(&sender_user)? > since
|| since == 0 || since == 0
{ {
db.users.count_one_time_keys(sender_user, sender_device)? db.users.count_one_time_keys(&sender_user, &sender_device)?
} else { } else {
BTreeMap::new() BTreeMap::new()
}, },
to_device: sync_events::ToDevice { to_device: sync_events::ToDevice {
events: db.users.get_to_device_events(sender_user, sender_device)?, events: db
.users
.get_to_device_events(&sender_user, &sender_device)?,
}, },
}; };
// TODO: Retry the endpoint instead of returning (waiting for #118) // TODO: Retry the endpoint instead of returning (waiting for #118)
if !body.full_state if !full_state
&& response.rooms.is_empty() && response.rooms.is_empty()
&& response.presence.is_empty() && response.presence.is_empty()
&& response.account_data.is_empty() && response.account_data.is_empty()
@@ -615,14 +760,15 @@ pub async fn sync_events_route(
{ {
// Hang a few seconds so requests are not spammed // Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives // Stop hanging if new info arrives
let mut duration = body.timeout.unwrap_or_default(); let mut duration = timeout.unwrap_or_default();
if duration.as_secs() > 30 { if duration.as_secs() > 30 {
duration = Duration::from_secs(30); duration = Duration::from_secs(30);
} }
let _ = tokio::time::timeout(duration, watcher).await; let _ = tokio::time::timeout(duration, watcher).await;
Ok((response, false))
} else {
Ok((response, since != next_batch)) // Only cache if we made progress
} }
Ok(response.into())
} }
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
@@ -631,9 +777,10 @@ fn share_encrypted_room(
sender_user: &UserId, sender_user: &UserId,
user_id: &UserId, user_id: &UserId,
ignore_room: &RoomId, ignore_room: &RoomId,
) -> bool { ) -> Result<bool> {
db.rooms Ok(db
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()]) .rooms
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.filter(|room_id| room_id != ignore_room) .filter(|room_id| room_id != ignore_room)
.filter_map(|other_room_id| { .filter_map(|other_room_id| {
@@ -644,5 +791,5 @@ fn share_encrypted_room(
.is_some(), .is_some(),
) )
}) })
.any(|encrypted| encrypted) .any(|encrypted| encrypted))
} }
+6 -6
View File
@@ -4,7 +4,7 @@ use ruma::{
api::client::r0::tag::{create_tag, delete_tag, get_tags}, api::client::r0::tag::{create_tag, delete_tag, get_tags},
events::EventType, events::EventType,
}; };
use std::collections::BTreeMap; use std::{collections::BTreeMap, sync::Arc};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, put}; use rocket::{delete, get, put};
@@ -15,7 +15,7 @@ use rocket::{delete, get, put};
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn update_tag_route( pub async fn update_tag_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_tag::Request<'_>>, body: Ruma<create_tag::Request<'_>>,
) -> ConduitResult<create_tag::Response> { ) -> ConduitResult<create_tag::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -31,7 +31,7 @@ pub async fn update_tag_route(
tags_event tags_event
.content .content
.tags .tags
.insert(body.tag.to_string(), body.tag_info.clone()); .insert(body.tag.clone().into(), body.tag_info.clone());
db.account_data.update( db.account_data.update(
Some(&body.room_id), Some(&body.room_id),
@@ -52,7 +52,7 @@ pub async fn update_tag_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn delete_tag_route( pub async fn delete_tag_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<delete_tag::Request<'_>>, body: Ruma<delete_tag::Request<'_>>,
) -> ConduitResult<delete_tag::Response> { ) -> ConduitResult<delete_tag::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -65,7 +65,7 @@ pub async fn delete_tag_route(
tags: BTreeMap::new(), tags: BTreeMap::new(),
}, },
}); });
tags_event.content.tags.remove(&body.tag); tags_event.content.tags.remove(&body.tag.clone().into());
db.account_data.update( db.account_data.update(
Some(&body.room_id), Some(&body.room_id),
@@ -86,7 +86,7 @@ pub async fn delete_tag_route(
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_tags_route( pub async fn get_tags_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<get_tags::Request<'_>>, body: Ruma<get_tags::Request<'_>>,
) -> ConduitResult<get_tags::Response> { ) -> ConduitResult<get_tags::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+21 -18
View File
@@ -1,8 +1,10 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Error, Ruma}; use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{ use ruma::{
error::ErrorKind, api::client::{error::ErrorKind, r0::to_device::send_event_to_device},
r0::to_device::{self, send_event_to_device}, to_device::DeviceIdOrAllDevices,
}; };
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@@ -14,13 +16,15 @@ use rocket::put;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_event_to_device_route( pub async fn send_event_to_device_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<send_event_to_device::Request<'_>>, body: Ruma<send_event_to_device::Request<'_>>,
) -> ConduitResult<send_event_to_device::Response> { ) -> ConduitResult<send_event_to_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref(); let sender_device = body.sender_device.as_deref();
// TODO: uncomment when https://github.com/vector-im/element-android/issues/3589 is solved
// Check if this is a new transaction id // Check if this is a new transaction id
/*
if db if db
.transaction_ids .transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)? .existing_txnid(sender_user, sender_device, &body.txn_id)?
@@ -28,31 +32,30 @@ pub async fn send_event_to_device_route(
{ {
return Ok(send_event_to_device::Response.into()); return Ok(send_event_to_device::Response.into());
} }
*/
for (target_user_id, map) in &body.messages { for (target_user_id, map) in &body.messages {
for (target_device_id_maybe, event) in map { for (target_device_id_maybe, event) in map {
match target_device_id_maybe { match target_device_id_maybe {
to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => { DeviceIdOrAllDevices::DeviceId(target_device_id) => db.users.add_to_device_event(
db.users.add_to_device_event( sender_user,
sender_user, &target_user_id,
&target_user_id, &target_device_id,
&target_device_id, &body.event_type,
&body.event_type, event.deserialize_as().map_err(|_| {
serde_json::from_str(event.get()).map_err(|_| { Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid") })?,
})?, &db.globals,
&db.globals, )?,
)?
}
to_device::DeviceIdOrAllDevices::AllDevices => { DeviceIdOrAllDevices::AllDevices => {
for target_device_id in db.users.all_device_ids(&target_user_id) { for target_device_id in db.users.all_device_ids(&target_user_id) {
db.users.add_to_device_event( db.users.add_to_device_event(
sender_user, sender_user,
&target_user_id, &target_user_id,
&target_device_id?, &target_device_id?,
&body.event_type, &body.event_type,
serde_json::from_str(event.get()).map_err(|_| { event.deserialize_as().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid") Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
})?, })?,
&db.globals, &db.globals,
+3 -1
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{utils, ConduitResult, Database, Ruma}; use crate::{utils, ConduitResult, Database, Ruma};
use create_typing_event::Typing; use create_typing_event::Typing;
@@ -12,7 +14,7 @@ use rocket::put;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub fn create_typing_event_route( pub fn create_typing_event_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<create_typing_event::Request<'_>>, body: Ruma<create_typing_event::Request<'_>>,
) -> ConduitResult<create_typing_event::Response> { ) -> ConduitResult<create_typing_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+12 -6
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::State; use super::State;
use crate::{ConduitResult, Database, Ruma}; use crate::{ConduitResult, Database, Ruma};
use ruma::api::client::r0::user_directory::search_users; use ruma::api::client::r0::user_directory::search_users;
@@ -11,7 +13,7 @@ use rocket::post;
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn search_users_route( pub async fn search_users_route(
db: State<'_, Database>, db: State<'_, Arc<Database>>,
body: Ruma<search_users::Request<'_>>, body: Ruma<search_users::Request<'_>>,
) -> ConduitResult<search_users::Response> { ) -> ConduitResult<search_users::Response> {
let limit = u64::from(body.limit) as usize; let limit = u64::from(body.limit) as usize;
@@ -19,9 +21,6 @@ pub async fn search_users_route(
let mut users = db.users.iter().filter_map(|user_id| { let mut users = db.users.iter().filter_map(|user_id| {
// Filter out buggy users (they should not exist, but you never know...) // Filter out buggy users (they should not exist, but you never know...)
let user_id = user_id.ok()?; let user_id = user_id.ok()?;
if db.users.is_deactivated(&user_id).ok()? {
return None;
}
let user = search_users::User { let user = search_users::User {
user_id: user_id.clone(), user_id: user_id.clone(),
@@ -29,11 +28,18 @@ pub async fn search_users_route(
avatar_url: db.users.avatar_url(&user_id).ok()?, avatar_url: db.users.avatar_url(&user_id).ok()?,
}; };
if !user.user_id.to_string().contains(&body.search_term) if !user
.user_id
.to_string()
.to_lowercase()
.contains(&body.search_term.to_lowercase())
&& user && user
.display_name .display_name
.as_ref() .as_ref()
.filter(|name| name.contains(&body.search_term)) .filter(|name| {
name.to_lowercase()
.contains(&body.search_term.to_lowercase())
})
.is_none() .is_none()
{ {
return None; return None;
+184 -81
View File
@@ -1,3 +1,5 @@
pub mod abstraction;
pub mod account_data; pub mod account_data;
pub mod admin; pub mod admin;
pub mod appservice; pub mod appservice;
@@ -11,16 +13,18 @@ pub mod transaction_ids;
pub mod uiaa; pub mod uiaa;
pub mod users; pub mod users;
use crate::{Error, Result}; use crate::{utils, Error, Result};
use abstraction::DatabaseEngine;
use directories::ProjectDirs; use directories::ProjectDirs;
use futures::StreamExt; use log::error;
use log::info; use lru_cache::LruCache;
use rocket::futures::{self, channel::mpsc}; use rocket::futures::{channel::mpsc, stream::FuturesUnordered, StreamExt};
use ruma::{DeviceId, ServerName, UserId}; use ruma::{DeviceId, ServerName, UserId};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::remove_dir_all, fs::{self, remove_dir_all},
io::Write,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
@@ -67,14 +71,19 @@ fn default_max_request_size() -> u32 {
} }
fn default_max_concurrent_requests() -> u16 { fn default_max_concurrent_requests() -> u16 {
4 100
} }
fn default_log() -> String { fn default_log() -> String {
"info,state_res=warn,rocket=off,_=off,sled=off".to_owned() "info,state_res=warn,rocket=off,_=off,sled=off".to_owned()
} }
#[derive(Clone)] #[cfg(feature = "sled")]
pub type Engine = abstraction::SledEngine;
#[cfg(feature = "rocksdb")]
pub type Engine = abstraction::RocksDbEngine;
pub struct Database { pub struct Database {
pub globals: globals::Globals, pub globals: globals::Globals,
pub users: users::Users, pub users: users::Users,
@@ -88,7 +97,6 @@ pub struct Database {
pub admin: admin::Admin, pub admin: admin::Admin,
pub appservice: appservice::Appservice, pub appservice: appservice::Appservice,
pub pusher: pusher::PushData, pub pusher: pusher::PushData,
pub _db: sled::Db,
} }
impl Database { impl Database {
@@ -105,118 +113,213 @@ impl Database {
} }
/// Load an existing database or create a new one. /// Load an existing database or create a new one.
pub async fn load_or_create(config: Config) -> Result<Self> { pub async fn load_or_create(config: Config) -> Result<Arc<Self>> {
let db = sled::Config::default() let builder = Engine::open(&config)?;
.path(&config.database_path)
.cache_capacity(config.cache_capacity as u64)
.use_compression(true)
.open()?;
info!("Opened sled database at {}", config.database_path); if config.max_request_size < 1024 {
eprintln!("ERROR: Max request size is less than 1KB. Please increase it.");
}
let (admin_sender, admin_receiver) = mpsc::unbounded(); let (admin_sender, admin_receiver) = mpsc::unbounded();
let (sending_sender, sending_receiver) = mpsc::unbounded();
let db = Self { let db = Arc::new(Self {
users: users::Users { users: users::Users {
userid_password: db.open_tree("userid_password")?, userid_password: builder.open_tree("userid_password")?,
userid_displayname: db.open_tree("userid_displayname")?, userid_displayname: builder.open_tree("userid_displayname")?,
userid_avatarurl: db.open_tree("userid_avatarurl")?, userid_avatarurl: builder.open_tree("userid_avatarurl")?,
userdeviceid_token: db.open_tree("userdeviceid_token")?, userdeviceid_token: builder.open_tree("userdeviceid_token")?,
userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?, userdeviceid_metadata: builder.open_tree("userdeviceid_metadata")?,
userid_devicelistversion: db.open_tree("userid_devicelistversion")?, userid_devicelistversion: builder.open_tree("userid_devicelistversion")?,
token_userdeviceid: db.open_tree("token_userdeviceid")?, token_userdeviceid: builder.open_tree("token_userdeviceid")?,
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?, onetimekeyid_onetimekeys: builder.open_tree("onetimekeyid_onetimekeys")?,
userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?, userid_lastonetimekeyupdate: builder.open_tree("userid_lastonetimekeyupdate")?,
keychangeid_userid: db.open_tree("keychangeid_userid")?, keychangeid_userid: builder.open_tree("keychangeid_userid")?,
keyid_key: db.open_tree("keyid_key")?, keyid_key: builder.open_tree("keyid_key")?,
userid_masterkeyid: db.open_tree("userid_masterkeyid")?, userid_masterkeyid: builder.open_tree("userid_masterkeyid")?,
userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?, userid_selfsigningkeyid: builder.open_tree("userid_selfsigningkeyid")?,
userid_usersigningkeyid: db.open_tree("userid_usersigningkeyid")?, userid_usersigningkeyid: builder.open_tree("userid_usersigningkeyid")?,
todeviceid_events: db.open_tree("todeviceid_events")?, todeviceid_events: builder.open_tree("todeviceid_events")?,
}, },
uiaa: uiaa::Uiaa { uiaa: uiaa::Uiaa {
userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo")?, userdevicesessionid_uiaainfo: builder.open_tree("userdevicesessionid_uiaainfo")?,
userdevicesessionid_uiaarequest: builder
.open_tree("userdevicesessionid_uiaarequest")?,
}, },
rooms: rooms::Rooms { rooms: rooms::Rooms {
edus: rooms::RoomEdus { edus: rooms::RoomEdus {
readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?, readreceiptid_readreceipt: builder.open_tree("readreceiptid_readreceipt")?,
roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt roomuserid_privateread: builder.open_tree("roomuserid_privateread")?, // "Private" read receipt
roomuserid_lastprivatereadupdate: db roomuserid_lastprivatereadupdate: builder
.open_tree("roomuserid_lastprivatereadupdate")?, .open_tree("roomuserid_lastprivatereadupdate")?,
typingid_userid: db.open_tree("typingid_userid")?, typingid_userid: builder.open_tree("typingid_userid")?,
roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?, roomid_lasttypingupdate: builder.open_tree("roomid_lasttypingupdate")?,
presenceid_presence: db.open_tree("presenceid_presence")?, presenceid_presence: builder.open_tree("presenceid_presence")?,
userid_lastpresenceupdate: db.open_tree("userid_lastpresenceupdate")?, userid_lastpresenceupdate: builder.open_tree("userid_lastpresenceupdate")?,
}, },
pduid_pdu: db.open_tree("pduid_pdu")?, pduid_pdu: builder.open_tree("pduid_pdu")?,
eventid_pduid: db.open_tree("eventid_pduid")?, eventid_pduid: builder.open_tree("eventid_pduid")?,
roomid_pduleaves: db.open_tree("roomid_pduleaves")?, roomid_pduleaves: builder.open_tree("roomid_pduleaves")?,
alias_roomid: db.open_tree("alias_roomid")?, alias_roomid: builder.open_tree("alias_roomid")?,
aliasid_alias: db.open_tree("aliasid_alias")?, aliasid_alias: builder.open_tree("aliasid_alias")?,
publicroomids: db.open_tree("publicroomids")?, publicroomids: builder.open_tree("publicroomids")?,
tokenids: db.open_tree("tokenids")?, tokenids: builder.open_tree("tokenids")?,
roomserverids: db.open_tree("roomserverids")?, roomserverids: builder.open_tree("roomserverids")?,
userroomid_joined: db.open_tree("userroomid_joined")?, serverroomids: builder.open_tree("serverroomids")?,
roomuserid_joined: db.open_tree("roomuserid_joined")?, userroomid_joined: builder.open_tree("userroomid_joined")?,
roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?, roomuserid_joined: builder.open_tree("roomuserid_joined")?,
userroomid_invitestate: db.open_tree("userroomid_invitestate")?, roomuseroncejoinedids: builder.open_tree("roomuseroncejoinedids")?,
roomuserid_invitecount: db.open_tree("roomuserid_invitecount")?, userroomid_invitestate: builder.open_tree("userroomid_invitestate")?,
userroomid_leftstate: db.open_tree("userroomid_leftstate")?, roomuserid_invitecount: builder.open_tree("roomuserid_invitecount")?,
roomuserid_leftcount: db.open_tree("roomuserid_leftcount")?, userroomid_leftstate: builder.open_tree("userroomid_leftstate")?,
roomuserid_leftcount: builder.open_tree("roomuserid_leftcount")?,
userroomid_notificationcount: db.open_tree("userroomid_notificationcount")?, userroomid_notificationcount: builder.open_tree("userroomid_notificationcount")?,
userroomid_highlightcount: db.open_tree("userroomid_highlightcount")?, userroomid_highlightcount: builder.open_tree("userroomid_highlightcount")?,
statekey_shortstatekey: db.open_tree("statekey_shortstatekey")?, statekey_shortstatekey: builder.open_tree("statekey_shortstatekey")?,
stateid_shorteventid: db.open_tree("stateid_shorteventid")?, stateid_shorteventid: builder.open_tree("stateid_shorteventid")?,
eventid_shorteventid: db.open_tree("eventid_shorteventid")?, eventid_shorteventid: builder.open_tree("eventid_shorteventid")?,
shorteventid_eventid: db.open_tree("shorteventid_eventid")?, shorteventid_eventid: builder.open_tree("shorteventid_eventid")?,
shorteventid_shortstatehash: db.open_tree("shorteventid_shortstatehash")?, shorteventid_shortstatehash: builder.open_tree("shorteventid_shortstatehash")?,
roomid_shortstatehash: db.open_tree("roomid_shortstatehash")?, roomid_shortstatehash: builder.open_tree("roomid_shortstatehash")?,
statehash_shortstatehash: db.open_tree("statehash_shortstatehash")?, statehash_shortstatehash: builder.open_tree("statehash_shortstatehash")?,
eventid_outlierpdu: db.open_tree("eventid_outlierpdu")?, eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
prevevent_parent: db.open_tree("prevevent_parent")?, prevevent_parent: builder.open_tree("prevevent_parent")?,
pdu_cache: RwLock::new(LruCache::new(1_000_000)),
}, },
account_data: account_data::AccountData { account_data: account_data::AccountData {
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?, roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
}, },
media: media::Media { media: media::Media {
mediaid_file: db.open_tree("mediaid_file")?, mediaid_file: builder.open_tree("mediaid_file")?,
}, },
key_backups: key_backups::KeyBackups { key_backups: key_backups::KeyBackups {
backupid_algorithm: db.open_tree("backupid_algorithm")?, backupid_algorithm: builder.open_tree("backupid_algorithm")?,
backupid_etag: db.open_tree("backupid_etag")?, backupid_etag: builder.open_tree("backupid_etag")?,
backupkeyid_backup: db.open_tree("backupkeyid_backup")?, backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
}, },
transaction_ids: transaction_ids::TransactionIds { transaction_ids: transaction_ids::TransactionIds {
userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?, userdevicetxnid_response: builder.open_tree("userdevicetxnid_response")?,
}, },
sending: sending::Sending { sending: sending::Sending {
servernamepduids: db.open_tree("servernamepduids")?, servername_educount: builder.open_tree("servername_educount")?,
servercurrentpdus: db.open_tree("servercurrentpdus")?, servernamepduids: builder.open_tree("servernamepduids")?,
servercurrentevents: builder.open_tree("servercurrentevents")?,
maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests as usize)), maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests as usize)),
sender: sending_sender,
}, },
admin: admin::Admin { admin: admin::Admin {
sender: admin_sender, sender: admin_sender,
}, },
appservice: appservice::Appservice { appservice: appservice::Appservice {
cached_registrations: Arc::new(RwLock::new(HashMap::new())), cached_registrations: Arc::new(RwLock::new(HashMap::new())),
id_appserviceregistrations: db.open_tree("id_appserviceregistrations")?, id_appserviceregistrations: builder.open_tree("id_appserviceregistrations")?,
},
pusher: pusher::PushData {
senderkey_pusher: builder.open_tree("senderkey_pusher")?,
}, },
pusher: pusher::PushData::new(&db)?,
globals: globals::Globals::load( globals: globals::Globals::load(
db.open_tree("global")?, builder.open_tree("global")?,
db.open_tree("servertimeout_signingkey")?, builder.open_tree("server_signingkeys")?,
config, config,
)?, )?,
_db: db, });
};
db.admin.start_handler(db.clone(), admin_receiver); // MIGRATIONS
// TODO: database versions of new dbs should probably not be 0
if db.globals.database_version()? < 1 {
for (roomserverid, _) in db.rooms.roomserverids.iter() {
let mut parts = roomserverid.split(|&b| b == 0xff);
let room_id = parts.next().expect("split always returns one element");
let servername = match parts.next() {
Some(s) => s,
None => {
error!("Migration: Invalid roomserverid in db.");
continue;
}
};
let mut serverroomid = servername.to_vec();
serverroomid.push(0xff);
serverroomid.extend_from_slice(room_id);
db.rooms.serverroomids.insert(&serverroomid, &[])?;
}
db.globals.bump_database_version(1)?;
println!("Migration: 0 -> 1 finished");
}
if db.globals.database_version()? < 2 {
// We accidentally inserted hashed versions of "" into the db instead of just ""
for (userid, password) in db.users.userid_password.iter() {
let password = utils::string_from_bytes(&password);
let empty_hashed_password = password.map_or(false, |password| {
argon2::verify_encoded(&password, b"").unwrap_or(false)
});
if empty_hashed_password {
db.users.userid_password.insert(&userid, b"")?;
}
}
db.globals.bump_database_version(2)?;
println!("Migration: 1 -> 2 finished");
}
if db.globals.database_version()? < 3 {
// Move media to filesystem
for (key, content) in db.media.mediaid_file.iter() {
if content.len() == 0 {
continue;
}
let path = db.globals.get_media_file(&key);
let mut file = fs::File::create(path)?;
file.write_all(&content)?;
db.media.mediaid_file.insert(&key, &[])?;
}
db.globals.bump_database_version(3)?;
println!("Migration: 2 -> 3 finished");
}
if db.globals.database_version()? < 4 {
// Add federated users to db as deactivated
for our_user in db.users.iter() {
let our_user = our_user?;
if db.users.is_deactivated(&our_user)? {
continue;
}
for room in db.rooms.rooms_joined(&our_user) {
for user in db.rooms.room_members(&room?) {
let user = user?;
if user.server_name() != db.globals.server_name() {
println!("Migration: Creating user {}", user);
db.users.create(&user, None)?;
}
}
}
}
db.globals.bump_database_version(4)?;
println!("Migration: 3 -> 4 finished");
}
// This data is probably outdated
db.rooms.edus.presenceid_presence.clear()?;
db.admin.start_handler(Arc::clone(&db), admin_receiver);
db.sending.start_handler(Arc::clone(&db), sending_receiver);
Ok(db) Ok(db)
} }
@@ -230,7 +333,7 @@ impl Database {
userdeviceid_prefix.extend_from_slice(device_id.as_bytes()); userdeviceid_prefix.extend_from_slice(device_id.as_bytes());
userdeviceid_prefix.push(0xff); userdeviceid_prefix.push(0xff);
let mut futures = futures::stream::FuturesUnordered::new(); let mut futures = FuturesUnordered::new();
// Return when *any* user changed his key // Return when *any* user changed his key
// TODO: only send for user they share a room with // TODO: only send for user they share a room with
+329
View File
@@ -0,0 +1,329 @@
use super::Config;
use crate::{utils, Result};
use log::warn;
use std::{future::Future, pin::Pin, sync::Arc};
#[cfg(feature = "rocksdb")]
use std::{collections::BTreeMap, sync::RwLock};
#[cfg(feature = "sled")]
pub struct SledEngine(sled::Db);
#[cfg(feature = "sled")]
pub struct SledEngineTree(sled::Tree);
#[cfg(feature = "rocksdb")]
pub struct RocksDbEngine(rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>);
#[cfg(feature = "rocksdb")]
pub struct RocksDbEngineTree<'a> {
db: Arc<RocksDbEngine>,
name: &'a str,
watchers: RwLock<BTreeMap<Vec<u8>, Vec<tokio::sync::oneshot::Sender<()>>>>,
}
pub trait DatabaseEngine: Sized {
fn open(config: &Config) -> Result<Arc<Self>>;
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>>;
}
pub trait Tree: Send + Sync {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>;
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()>;
fn remove(&self, key: &[u8]) -> Result<()>;
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + Sync + 'a>;
fn iter_from<'a>(
&'a self,
from: &[u8],
backwards: bool,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a>;
fn increment(&self, key: &[u8]) -> Result<Vec<u8>>;
fn scan_prefix<'a>(
&'a self,
prefix: Vec<u8>,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + 'a>;
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
fn clear(&self) -> Result<()> {
for (key, _) in self.iter() {
self.remove(&key)?;
}
Ok(())
}
}
#[cfg(feature = "sled")]
impl DatabaseEngine for SledEngine {
fn open(config: &Config) -> Result<Arc<Self>> {
Ok(Arc::new(SledEngine(
sled::Config::default()
.path(&config.database_path)
.cache_capacity(config.cache_capacity as u64)
.use_compression(true)
.open()?,
)))
}
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>> {
Ok(Arc::new(SledEngineTree(self.0.open_tree(name)?)))
}
}
#[cfg(feature = "sled")]
impl Tree for SledEngineTree {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
Ok(self.0.get(key)?.map(|v| v.to_vec()))
}
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
self.0.insert(key, value)?;
Ok(())
}
fn remove(&self, key: &[u8]) -> Result<()> {
self.0.remove(key)?;
Ok(())
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + Sync + 'a> {
Box::new(
self.0
.iter()
.filter_map(|r| {
if let Err(e) = &r {
warn!("Error: {}", e);
}
r.ok()
})
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into())),
)
}
fn iter_from(
&self,
from: &[u8],
backwards: bool,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)>> {
let iter = if backwards {
self.0.range(..=from)
} else {
self.0.range(from..)
};
let iter = iter
.filter_map(|r| {
if let Err(e) = &r {
warn!("Error: {}", e);
}
r.ok()
})
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into()));
if backwards {
Box::new(iter.rev())
} else {
Box::new(iter)
}
}
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
Ok(self
.0
.update_and_fetch(key, utils::increment)
.map(|o| o.expect("increment always sets a value").to_vec())?)
}
fn scan_prefix<'a>(
&'a self,
prefix: Vec<u8>,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + 'a> {
let iter = self
.0
.scan_prefix(prefix)
.filter_map(|r| {
if let Err(e) = &r {
warn!("Error: {}", e);
}
r.ok()
})
.map(|(k, v)| (k.to_vec().into(), v.to_vec().into()));
Box::new(iter)
}
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let prefix = prefix.to_vec();
Box::pin(async move {
self.0.watch_prefix(prefix).await;
})
}
}
#[cfg(feature = "rocksdb")]
impl DatabaseEngine for RocksDbEngine {
fn open(config: &Config) -> Result<Arc<Self>> {
let mut db_opts = rocksdb::Options::default();
db_opts.create_if_missing(true);
db_opts.set_max_open_files(16);
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
db_opts.set_compression_type(rocksdb::DBCompressionType::Snappy);
db_opts.set_target_file_size_base(256 << 20);
db_opts.set_write_buffer_size(256 << 20);
let mut block_based_options = rocksdb::BlockBasedOptions::default();
block_based_options.set_block_size(512 << 10);
db_opts.set_block_based_table_factory(&block_based_options);
let cfs = rocksdb::DBWithThreadMode::<rocksdb::MultiThreaded>::list_cf(
&db_opts,
&config.database_path,
)
.unwrap_or_default();
let mut options = rocksdb::Options::default();
options.set_merge_operator_associative("increment", utils::increment_rocksdb);
let db = rocksdb::DBWithThreadMode::<rocksdb::MultiThreaded>::open_cf_descriptors(
&db_opts,
&config.database_path,
cfs.iter()
.map(|name| rocksdb::ColumnFamilyDescriptor::new(name, options.clone())),
)?;
Ok(Arc::new(RocksDbEngine(db)))
}
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>> {
let mut options = rocksdb::Options::default();
options.set_merge_operator_associative("increment", utils::increment_rocksdb);
// Create if it doesn't exist
let _ = self.0.create_cf(name, &options);
Ok(Arc::new(RocksDbEngineTree {
name,
db: Arc::clone(self),
watchers: RwLock::new(BTreeMap::new()),
}))
}
}
#[cfg(feature = "rocksdb")]
impl RocksDbEngineTree<'_> {
fn cf(&self) -> rocksdb::BoundColumnFamily<'_> {
self.db.0.cf_handle(self.name).unwrap()
}
}
#[cfg(feature = "rocksdb")]
impl Tree for RocksDbEngineTree<'_> {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
Ok(self.db.0.get_cf(self.cf(), key)?)
}
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
let watchers = self.watchers.read().unwrap();
let mut triggered = Vec::new();
for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) {
triggered.push(&key[..length]);
}
}
drop(watchers);
if !triggered.is_empty() {
let mut watchers = self.watchers.write().unwrap();
for prefix in triggered {
if let Some(txs) = watchers.remove(prefix) {
for tx in txs {
let _ = tx.send(());
}
}
}
}
Ok(self.db.0.put_cf(self.cf(), key, value)?)
}
fn remove(&self, key: &[u8]) -> Result<()> {
Ok(self.db.0.delete_cf(self.cf(), key)?)
}
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + Sync + 'a> {
Box::new(
self.db
.0
.iterator_cf(self.cf(), rocksdb::IteratorMode::Start),
)
}
fn iter_from<'a>(
&'a self,
from: &[u8],
backwards: bool,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
Box::new(self.db.0.iterator_cf(
self.cf(),
rocksdb::IteratorMode::From(
from,
if backwards {
rocksdb::Direction::Reverse
} else {
rocksdb::Direction::Forward
},
),
))
}
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
let stats = rocksdb::perf::get_memory_usage_stats(Some(&[&self.db.0]), None).unwrap();
dbg!(stats.mem_table_total);
dbg!(stats.mem_table_unflushed);
dbg!(stats.mem_table_readers_total);
dbg!(stats.cache_total);
// TODO: atomic?
let old = self.get(key)?;
let new = utils::increment(old.as_deref()).unwrap();
self.insert(key, &new)?;
Ok(new)
}
fn scan_prefix<'a>(
&'a self,
prefix: Vec<u8>,
) -> Box<dyn Iterator<Item = (Box<[u8]>, Box<[u8]>)> + Send + 'a> {
Box::new(
self.db
.0
.iterator_cf(
self.cf(),
rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward),
)
.take_while(move |(k, _)| k.starts_with(&prefix)),
)
}
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let (tx, rx) = tokio::sync::oneshot::channel();
self.watchers
.write()
.unwrap()
.entry(prefix.to_vec())
.or_default()
.push(tx);
Box::pin(async move {
// Tx is never destroyed
rx.await.unwrap();
})
}
}
+30 -33
View File
@@ -1,17 +1,17 @@
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use ruma::{ use ruma::{
api::client::error::ErrorKind, api::client::error::ErrorKind,
events::{AnyEvent as EduEvent, EventType}, events::{AnyEphemeralRoomEvent, EventType},
serde::Raw, serde::Raw,
RoomId, UserId, RoomId, UserId,
}; };
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use sled::IVec; use std::{collections::HashMap, convert::TryFrom, sync::Arc};
use std::{collections::HashMap, convert::TryFrom};
use super::abstraction::Tree;
#[derive(Clone)]
pub struct AccountData { pub struct AccountData {
pub(super) roomuserdataid_accountdata: sled::Tree, // RoomUserDataId = Room + User + Count + Type pub(super) roomuserdataid_accountdata: Arc<dyn Tree>, // RoomUserDataId = Room + User + Count + Type
} }
impl AccountData { impl AccountData {
@@ -34,9 +34,8 @@ impl AccountData {
prefix.push(0xff); prefix.push(0xff);
// Remove old entry // Remove old entry
if let Some(previous) = self.find_event(room_id, user_id, &event_type) { if let Some((old_key, _)) = self.find_event(room_id, user_id, &event_type)? {
let (old_key, _) = previous?; self.roomuserdataid_accountdata.remove(&old_key)?;
self.roomuserdataid_accountdata.remove(old_key)?;
} }
let mut key = prefix; let mut key = prefix;
@@ -52,8 +51,10 @@ impl AccountData {
)); ));
} }
self.roomuserdataid_accountdata self.roomuserdataid_accountdata.insert(
.insert(key, &*json.to_string())?; &key,
&serde_json::to_vec(&json).expect("to_vec always works on json values"),
)?;
Ok(()) Ok(())
} }
@@ -65,9 +66,8 @@ impl AccountData {
user_id: &UserId, user_id: &UserId,
kind: EventType, kind: EventType,
) -> Result<Option<T>> { ) -> Result<Option<T>> {
self.find_event(room_id, user_id, &kind) self.find_event(room_id, user_id, &kind)?
.map(|r| { .map(|(_, v)| {
let (_, v) = r?;
serde_json::from_slice(&v).map_err(|_| Error::bad_database("could not deserialize")) serde_json::from_slice(&v).map_err(|_| Error::bad_database("could not deserialize"))
}) })
.transpose() .transpose()
@@ -80,7 +80,7 @@ impl AccountData {
room_id: Option<&RoomId>, room_id: Option<&RoomId>,
user_id: &UserId, user_id: &UserId,
since: u64, since: u64,
) -> Result<HashMap<EventType, Raw<EduEvent>>> { ) -> Result<HashMap<EventType, Raw<AnyEphemeralRoomEvent>>> {
let mut userdata = HashMap::new(); let mut userdata = HashMap::new();
let mut prefix = room_id let mut prefix = room_id
@@ -98,8 +98,7 @@ impl AccountData {
for r in self for r in self
.roomuserdataid_accountdata .roomuserdataid_accountdata
.range(&*first_possible..) .iter_from(&first_possible, false)
.filter_map(|r| r.ok())
.take_while(move |(k, _)| k.starts_with(&prefix)) .take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(k, v)| { .map(|(k, v)| {
Ok::<_, Error>(( Ok::<_, Error>((
@@ -110,7 +109,7 @@ impl AccountData {
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?, .map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
) )
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?, .map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
serde_json::from_slice::<Raw<EduEvent>>(&v).map_err(|_| { serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
Error::bad_database("Database contains invalid account data.") Error::bad_database("Database contains invalid account data.")
})?, })?,
)) ))
@@ -128,7 +127,7 @@ impl AccountData {
room_id: Option<&RoomId>, room_id: Option<&RoomId>,
user_id: &UserId, user_id: &UserId,
kind: &EventType, kind: &EventType,
) -> Option<Result<(IVec, IVec)>> { ) -> Result<Option<(Box<[u8]>, Box<[u8]>)>> {
let mut prefix = room_id let mut prefix = room_id
.map(|r| r.to_string()) .map(|r| r.to_string())
.unwrap_or_default() .unwrap_or_default()
@@ -137,23 +136,21 @@ impl AccountData {
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(&user_id.as_bytes()); prefix.extend_from_slice(&user_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
let mut last_possible_key = prefix.clone();
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
let kind = kind.clone(); let kind = kind.clone();
self.roomuserdataid_accountdata Ok(self
.scan_prefix(prefix) .roomuserdataid_accountdata
.rev() .iter_from(&last_possible_key, true)
.find(move |r| { .take_while(move |(k, _)| k.starts_with(&prefix))
r.as_ref() .find(move |(k, _)| {
.map(|(k, _)| { k.rsplit(|&b| b == 0xff)
k.rsplit(|&b| b == 0xff) .next()
.next() .map(|current_event_type| current_event_type == kind.as_ref().as_bytes())
.map(|current_event_type| {
current_event_type == kind.as_ref().as_bytes()
})
.unwrap_or(false)
})
.unwrap_or(false) .unwrap_or(false)
}) }))
.map(|r| Ok(r?))
} }
} }
+18 -12
View File
@@ -1,6 +1,9 @@
use std::convert::{TryFrom, TryInto}; use std::{
convert::{TryFrom, TryInto},
sync::Arc,
};
use crate::pdu::PduBuilder; use crate::{pdu::PduBuilder, Database};
use log::warn; use log::warn;
use rocket::futures::{channel::mpsc, stream::StreamExt}; use rocket::futures::{channel::mpsc, stream::StreamExt};
use ruma::{ use ruma::{
@@ -22,7 +25,7 @@ pub struct Admin {
impl Admin { impl Admin {
pub fn start_handler( pub fn start_handler(
&self, &self,
db: super::Database, db: Arc<Database>,
mut receiver: mpsc::UnboundedReceiver<AdminCommand>, mut receiver: mpsc::UnboundedReceiver<AdminCommand>,
) { ) {
tokio::spawn(async move { tokio::spawn(async move {
@@ -73,14 +76,17 @@ impl Admin {
db.appservice.register_appservice(yaml).unwrap(); // TODO handle error db.appservice.register_appservice(yaml).unwrap(); // TODO handle error
} }
AdminCommand::ListAppservices => { AdminCommand::ListAppservices => {
let appservices = db.appservice.iter_ids().collect::<Vec<_>>(); if let Ok(appservices) = db.appservice.iter_ids().map(|ids| ids.collect::<Vec<_>>()) {
let count = appservices.len(); let count = appservices.len();
let output = format!( let output = format!(
"Appservices ({}): {}", "Appservices ({}): {}",
count, count,
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ") appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
); );
send_message(message::MessageEventContent::text_plain(output)); send_message(message::MessageEventContent::text_plain(output));
} else {
send_message(message::MessageEventContent::text_plain("Failed to get appservices."));
}
} }
AdminCommand::SendMessage(message) => { AdminCommand::SendMessage(message) => {
send_message(message); send_message(message);
@@ -93,6 +99,6 @@ impl Admin {
} }
pub fn send(&self, command: AdminCommand) { pub fn send(&self, command: AdminCommand) {
self.sender.unbounded_send(command).unwrap() self.sender.unbounded_send(command).unwrap();
} }
} }
+22 -19
View File
@@ -4,18 +4,21 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
#[derive(Clone)] use super::abstraction::Tree;
pub struct Appservice { pub struct Appservice {
pub(super) cached_registrations: Arc<RwLock<HashMap<String, serde_yaml::Value>>>, pub(super) cached_registrations: Arc<RwLock<HashMap<String, serde_yaml::Value>>>,
pub(super) id_appserviceregistrations: sled::Tree, pub(super) id_appserviceregistrations: Arc<dyn Tree>,
} }
impl Appservice { impl Appservice {
pub fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<()> { pub fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<()> {
// TODO: Rumaify // TODO: Rumaify
let id = yaml.get("id").unwrap().as_str().unwrap(); let id = yaml.get("id").unwrap().as_str().unwrap();
self.id_appserviceregistrations self.id_appserviceregistrations.insert(
.insert(id, serde_yaml::to_string(&yaml).unwrap().as_bytes())?; id.as_bytes(),
serde_yaml::to_string(&yaml).unwrap().as_bytes(),
)?;
self.cached_registrations self.cached_registrations
.write() .write()
.unwrap() .unwrap()
@@ -31,37 +34,37 @@ impl Appservice {
.get(id) .get(id)
.map_or_else( .map_or_else(
|| { || {
Ok(self self.id_appserviceregistrations
.id_appserviceregistrations .get(id.as_bytes())?
.get(id)?
.map(|bytes| { .map(|bytes| {
Ok::<_, Error>(serde_yaml::from_slice(&bytes).map_err(|_| { serde_yaml::from_slice(&bytes).map_err(|_| {
Error::bad_database( Error::bad_database(
"Invalid registration bytes in id_appserviceregistrations.", "Invalid registration bytes in id_appserviceregistrations.",
) )
})?) })
}) })
.transpose()?) .transpose()
}, },
|r| Ok(Some(r.clone())), |r| Ok(Some(r.clone())),
) )
} }
pub fn iter_ids(&self) -> impl Iterator<Item = Result<String>> { pub fn iter_ids(&self) -> Result<impl Iterator<Item = Result<String>> + Send + Sync + '_> {
self.id_appserviceregistrations.iter().keys().map(|id| { Ok(self.id_appserviceregistrations.iter().map(|(id, _)| {
Ok(utils::string_from_bytes(&id?).map_err(|_| { utils::string_from_bytes(&id)
Error::bad_database("Invalid id bytes in id_appserviceregistrations.") .map_err(|_| Error::bad_database("Invalid id bytes in id_appserviceregistrations."))
})?) }))
})
} }
pub fn iter_all(&self) -> impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ { pub fn iter_all(
self.iter_ids().filter_map(|id| id.ok()).map(move |id| { &self,
) -> Result<impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ + Send + Sync> {
Ok(self.iter_ids()?.filter_map(|id| id.ok()).map(move |id| {
Ok(( Ok((
id.clone(), id.clone(),
self.get_registration(&id)? self.get_registration(&id)?
.expect("iter_ids only returns appservices that exist"), .expect("iter_ids only returns appservices that exist"),
)) ))
}) }))
} }
} }
+118 -68
View File
@@ -1,32 +1,50 @@
use crate::{database::Config, utils, Error, Result}; use crate::{database::Config, utils, ConduitResult, Error, Result};
use log::{error, info}; use log::{error, info};
use ruma::{ use ruma::{DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, ServerName, ServerSigningKeyId, UserId, api::{
api::federation::discovery::{ServerSigningKeys, VerifyKey}, client::r0::sync::sync_events,
ServerName, ServerSigningKeyId, federation::discovery::{ServerSigningKeys, VerifyKey},
}; }};
use rustls::{ServerCertVerifier, WebPKIVerifier}; use rustls::{ServerCertVerifier, WebPKIVerifier};
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
fs,
path::PathBuf,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
time::Duration, time::{Duration, Instant},
}; };
use tokio::sync::{Mutex, Semaphore};
use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::TokioAsyncResolver;
pub const COUNTER: &str = "c"; use super::abstraction::Tree;
pub const COUNTER: &[u8] = b"c";
type WellKnownMap = HashMap<Box<ServerName>, (String, String)>; type WellKnownMap = HashMap<Box<ServerName>, (String, String)>;
type TlsNameMap = HashMap<String, webpki::DNSName>; type TlsNameMap = HashMap<String, webpki::DNSName>;
#[derive(Clone)] type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
pub struct Globals { pub struct Globals {
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
pub tls_name_override: Arc<RwLock<TlsNameMap>>, pub tls_name_override: Arc<RwLock<TlsNameMap>>,
pub(super) globals: sled::Tree, pub(super) globals: Arc<dyn Tree>,
config: Config, config: Config,
keypair: Arc<ruma::signatures::Ed25519KeyPair>, keypair: Arc<ruma::signatures::Ed25519KeyPair>,
reqwest_client: reqwest::Client, reqwest_client: reqwest::Client,
dns_resolver: TokioAsyncResolver, dns_resolver: TokioAsyncResolver,
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>, jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
pub(super) servertimeout_signingkey: sled::Tree, // ServerName + Timeout Timestamp -> algorithm:key + pubkey pub(super) server_signingkeys: Arc<dyn Tree>,
pub bad_event_ratelimiter: Arc<RwLock<BTreeMap<EventId, RateLimitState>>>,
pub bad_signature_ratelimiter: Arc<RwLock<BTreeMap<Vec<String>, RateLimitState>>>,
pub servername_ratelimiter: Arc<RwLock<BTreeMap<Box<ServerName>, Arc<Semaphore>>>>,
pub roomid_mutex: RwLock<BTreeMap<RoomId, Arc<Mutex<()>>>>,
pub sync_receivers: RwLock<
BTreeMap<
(UserId, Box<DeviceId>),
(
Option<String>,
tokio::sync::watch::Receiver<Option<ConduitResult<sync_events::Response>>>,
), // since, rx
>,
>,
} }
struct MatrixServerVerifier { struct MatrixServerVerifier {
@@ -64,15 +82,20 @@ impl ServerCertVerifier for MatrixServerVerifier {
impl Globals { impl Globals {
pub fn load( pub fn load(
globals: sled::Tree, globals: Arc<dyn Tree>,
servertimeout_signingkey: sled::Tree, server_signingkeys: Arc<dyn Tree>,
config: Config, config: Config,
) -> Result<Self> { ) -> Result<Self> {
let bytes = &*globals let keypair_bytes = globals.get(b"keypair")?.map_or_else(
.update_and_fetch("keypair", utils::generate_keypair)? || {
.expect("utils::generate_keypair always returns Some"); let keypair = utils::generate_keypair();
globals.insert(b"keypair", &keypair)?;
Ok::<_, Error>(keypair)
},
|s| Ok(s.to_vec()),
)?;
let mut parts = bytes.splitn(2, |&b| b == 0xff); let mut parts = keypair_bytes.splitn(2, |&b| b == 0xff);
let keypair = utils::string_from_bytes( let keypair = utils::string_from_bytes(
// 1. version // 1. version
@@ -89,7 +112,7 @@ impl Globals {
.map(|key| (version, key)) .map(|key| (version, key))
}) })
.and_then(|(version, key)| { .and_then(|(version, key)| {
ruma::signatures::Ed25519KeyPair::new(&key, version) ruma::signatures::Ed25519KeyPair::from_der(&key, version)
.map_err(|_| Error::bad_database("Private or public keys are invalid.")) .map_err(|_| Error::bad_database("Private or public keys are invalid."))
}); });
@@ -97,7 +120,7 @@ impl Globals {
Ok(k) => k, Ok(k) => k,
Err(e) => { Err(e) => {
error!("Keypair invalid. Deleting..."); error!("Keypair invalid. Deleting...");
globals.remove("keypair")?; globals.remove(b"keypair")?;
return Err(e); return Err(e);
} }
}; };
@@ -125,7 +148,7 @@ impl Globals {
.as_ref() .as_ref()
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static()); .map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static());
Ok(Self { let s = Self {
globals, globals,
config, config,
keypair: Arc::new(keypair), keypair: Arc::new(keypair),
@@ -135,9 +158,18 @@ impl Globals {
})?, })?,
actual_destination_cache: Arc::new(RwLock::new(WellKnownMap::new())), actual_destination_cache: Arc::new(RwLock::new(WellKnownMap::new())),
tls_name_override, tls_name_override,
servertimeout_signingkey, server_signingkeys,
jwt_decoding_key, jwt_decoding_key,
}) bad_event_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
bad_signature_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
servername_ratelimiter: Arc::new(RwLock::new(BTreeMap::new())),
roomid_mutex: RwLock::new(BTreeMap::new()),
sync_receivers: RwLock::new(BTreeMap::new()),
};
fs::create_dir_all(s.get_media_folder())?;
Ok(s)
} }
/// Returns this server's keypair. /// Returns this server's keypair.
@@ -151,19 +183,14 @@ impl Globals {
} }
pub fn next_count(&self) -> Result<u64> { pub fn next_count(&self) -> Result<u64> {
Ok(utils::u64_from_bytes( utils::u64_from_bytes(&self.globals.increment(COUNTER)?)
&self .map_err(|_| Error::bad_database("Count has invalid bytes."))
.globals
.update_and_fetch(COUNTER, utils::increment)?
.expect("utils::increment will always put in a value"),
)
.map_err(|_| Error::bad_database("Count has invalid bytes."))?)
} }
pub fn current_count(&self) -> Result<u64> { pub fn current_count(&self) -> Result<u64> {
self.globals.get(COUNTER)?.map_or(Ok(0_u64), |bytes| { self.globals.get(COUNTER)?.map_or(Ok(0_u64), |bytes| {
Ok(utils::u64_from_bytes(&bytes) utils::u64_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Count has invalid bytes."))?) .map_err(|_| Error::bad_database("Count has invalid bytes."))
}) })
} }
@@ -203,30 +230,29 @@ impl Globals {
/// Remove the outdated keys and insert the new ones. /// Remove the outdated keys and insert the new ones.
/// ///
/// This doesn't actually check that the keys provided are newer than the old set. /// This doesn't actually check that the keys provided are newer than the old set.
pub fn add_signing_key(&self, origin: &ServerName, keys: &ServerSigningKeys) -> Result<()> { pub fn add_signing_key(&self, origin: &ServerName, new_keys: ServerSigningKeys) -> Result<()> {
let mut key1 = origin.as_bytes().to_vec(); // Not atomic, but this is not critical
key1.push(0xff); let signingkeys = self.server_signingkeys.get(origin.as_bytes())?;
let mut key2 = key1.clone(); let mut keys = signingkeys
.and_then(|keys| serde_json::from_slice(&keys).ok())
.unwrap_or_else(|| {
// Just insert "now", it doesn't matter
ServerSigningKeys::new(origin.to_owned(), MilliSecondsSinceUnixEpoch::now())
});
let ts = keys let ServerSigningKeys {
.valid_until_ts verify_keys,
.duration_since(std::time::UNIX_EPOCH) old_verify_keys,
.expect("time is valid") ..
.as_millis() as u64; } = new_keys;
key1.extend_from_slice(&ts.to_be_bytes()); keys.verify_keys.extend(verify_keys.into_iter());
key2.extend_from_slice(&(ts + 1).to_be_bytes()); keys.old_verify_keys.extend(old_verify_keys.into_iter());
self.servertimeout_signingkey.insert( self.server_signingkeys.insert(
key1, origin.as_bytes(),
serde_json::to_vec(&keys.verify_keys).expect("ServerSigningKeys are a valid string"), &serde_json::to_vec(&keys).expect("serversigningkeys can be serialized"),
)?;
self.servertimeout_signingkey.insert(
key2,
serde_json::to_vec(&keys.old_verify_keys)
.expect("ServerSigningKeys are a valid string"),
)?; )?;
Ok(()) Ok(())
@@ -237,25 +263,49 @@ impl Globals {
&self, &self,
origin: &ServerName, origin: &ServerName,
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> { ) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
let mut response = BTreeMap::new(); let signingkeys = self
.server_signingkeys
.get(origin.as_bytes())?
.and_then(|bytes| serde_json::from_slice::<ServerSigningKeys>(&bytes).ok())
.map(|keys| {
let mut tree = keys.verify_keys;
tree.extend(
keys.old_verify_keys
.into_iter()
.map(|old| (old.0, VerifyKey::new(old.1.key))),
);
tree
})
.unwrap_or_else(BTreeMap::new);
let now = crate::utils::millis_since_unix_epoch(); Ok(signingkeys)
}
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) { pub fn database_version(&self) -> Result<u64> {
let (k, bytes) = item?; self.globals.get(b"version")?.map_or(Ok(0), |version| {
let valid_until = k utils::u64_from_bytes(&version)
.splitn(2, |&b| b == 0xff) .map_err(|_| Error::bad_database("Database version id is invalid."))
.nth(1) })
.map(crate::utils::u64_from_bytes) }
.ok_or_else(|| Error::bad_database("Invalid signing keys."))?
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?; pub fn bump_database_version(&self, new_version: u64) -> Result<()> {
// If these keys are still valid use em! self.globals
if valid_until > now { .insert(b"version", &new_version.to_be_bytes())?;
let btree: BTreeMap<_, _> = serde_json::from_slice(&bytes) Ok(())
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"))?; }
response.extend(btree);
} pub fn get_media_folder(&self) -> PathBuf {
} let mut r = PathBuf::new();
Ok(response) r.push(self.config.database_path.clone());
r.push("media");
r
}
pub fn get_media_file(&self, key: &[u8]) -> PathBuf {
let mut r = PathBuf::new();
r.push(self.config.database_path.clone());
r.push("media");
r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
r
} }
} }
+65 -75
View File
@@ -6,13 +6,14 @@ use ruma::{
}, },
RoomId, UserId, RoomId, UserId,
}; };
use std::{collections::BTreeMap, convert::TryFrom}; use std::{collections::BTreeMap, convert::TryFrom, sync::Arc};
use super::abstraction::Tree;
#[derive(Clone)]
pub struct KeyBackups { pub struct KeyBackups {
pub(super) backupid_algorithm: sled::Tree, // BackupId = UserId + Version(Count) pub(super) backupid_algorithm: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
pub(super) backupid_etag: sled::Tree, // BackupId = UserId + Version(Count) pub(super) backupid_etag: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
pub(super) backupkeyid_backup: sled::Tree, // BackupKeyId = UserId + Version + RoomId + SessionId pub(super) backupkeyid_backup: Arc<dyn Tree>, // BackupKeyId = UserId + Version + RoomId + SessionId
} }
impl KeyBackups { impl KeyBackups {
@@ -30,8 +31,7 @@ impl KeyBackups {
self.backupid_algorithm.insert( self.backupid_algorithm.insert(
&key, &key,
&*serde_json::to_string(backup_metadata) &serde_json::to_vec(backup_metadata).expect("BackupAlgorithm::to_vec always works"),
.expect("BackupAlgorithm::to_string always works"),
)?; )?;
self.backupid_etag self.backupid_etag
.insert(&key, &globals.next_count()?.to_be_bytes())?; .insert(&key, &globals.next_count()?.to_be_bytes())?;
@@ -48,13 +48,8 @@ impl KeyBackups {
key.push(0xff); key.push(0xff);
for outdated_key in self for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
.backupkeyid_backup self.backupkeyid_backup.remove(&outdated_key)?;
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
} }
Ok(()) Ok(())
@@ -80,8 +75,9 @@ impl KeyBackups {
self.backupid_algorithm.insert( self.backupid_algorithm.insert(
&key, &key,
&*serde_json::to_string(backup_metadata) &serde_json::to_string(backup_metadata)
.expect("BackupAlgorithm::to_string always works"), .expect("BackupAlgorithm::to_string always works")
.as_bytes(),
)?; )?;
self.backupid_etag self.backupid_etag
.insert(&key, &globals.next_count()?.to_be_bytes())?; .insert(&key, &globals.next_count()?.to_be_bytes())?;
@@ -91,11 +87,14 @@ impl KeyBackups {
pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> { pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
let mut prefix = user_id.as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut last_possible_key = prefix.clone();
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
self.backupid_algorithm self.backupid_algorithm
.scan_prefix(&prefix) .iter_from(&last_possible_key, true)
.last() .take_while(move |(k, _)| k.starts_with(&prefix))
.map_or(Ok(None), |r| { .next()
let (key, value) = r?; .map_or(Ok(None), |(key, value)| {
let version = utils::string_from_bytes( let version = utils::string_from_bytes(
key.rsplit(|&b| b == 0xff) key.rsplit(|&b| b == 0xff)
.next() .next()
@@ -117,10 +116,12 @@ impl KeyBackups {
key.push(0xff); key.push(0xff);
key.extend_from_slice(version.as_bytes()); key.extend_from_slice(version.as_bytes());
self.backupid_algorithm.get(key)?.map_or(Ok(None), |bytes| { self.backupid_algorithm
Ok(serde_json::from_slice(&bytes) .get(&key)?
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))?) .map_or(Ok(None), |bytes| {
}) serde_json::from_slice(&bytes)
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))
})
} }
pub fn add_key( pub fn add_key(
@@ -153,7 +154,7 @@ impl KeyBackups {
self.backupkeyid_backup.insert( self.backupkeyid_backup.insert(
&key, &key,
&*serde_json::to_string(&key_data).expect("KeyBackupData::to_string always works"), &serde_json::to_vec(&key_data).expect("KeyBackupData::to_vec always works"),
)?; )?;
Ok(()) Ok(())
@@ -164,7 +165,7 @@ impl KeyBackups {
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes()); prefix.extend_from_slice(version.as_bytes());
Ok(self.backupkeyid_backup.scan_prefix(&prefix).count()) Ok(self.backupkeyid_backup.scan_prefix(prefix).count())
} }
pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> { pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> {
@@ -194,33 +195,37 @@ impl KeyBackups {
let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new(); let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new();
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| { for result in self
let (key, value) = r?; .backupkeyid_backup
let mut parts = key.rsplit(|&b| b == 0xff); .scan_prefix(prefix)
.map(|(key, value)| {
let mut parts = key.rsplit(|&b| b == 0xff);
let session_id = utils::string_from_bytes( let session_id =
&parts utils::string_from_bytes(&parts.next().ok_or_else(|| {
.next() Error::bad_database("backupkeyid_backup key is invalid.")
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?, })?)
) .map_err(|_| {
.map_err(|_| Error::bad_database("backupkeyid_backup session_id is invalid."))?; Error::bad_database("backupkeyid_backup session_id is invalid.")
})?;
let room_id = RoomId::try_from( let room_id = RoomId::try_from(
utils::string_from_bytes( utils::string_from_bytes(&parts.next().ok_or_else(|| {
&parts Error::bad_database("backupkeyid_backup key is invalid.")
.next() })?)
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?, .map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?,
) )
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?, .map_err(|_| {
) Error::bad_database("backupkeyid_backup room_id is invalid room id.")
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?; })?;
let key_data = serde_json::from_slice(&value).map_err(|_| { let key_data = serde_json::from_slice(&value).map_err(|_| {
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.") Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
})?; })?;
Ok::<_, Error>((room_id, session_id, key_data)) Ok::<_, Error>((room_id, session_id, key_data))
}) { })
{
let (room_id, session_id, key_data) = result?; let (room_id, session_id, key_data) = result?;
rooms rooms
.entry(room_id) .entry(room_id)
@@ -239,7 +244,7 @@ impl KeyBackups {
user_id: &UserId, user_id: &UserId,
version: &str, version: &str,
room_id: &RoomId, room_id: &RoomId,
) -> BTreeMap<String, KeyBackupData> { ) -> Result<BTreeMap<String, KeyBackupData>> {
let mut prefix = user_id.as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes()); prefix.extend_from_slice(version.as_bytes());
@@ -247,10 +252,10 @@ impl KeyBackups {
prefix.extend_from_slice(room_id.as_bytes()); prefix.extend_from_slice(room_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
self.backupkeyid_backup Ok(self
.scan_prefix(&prefix) .backupkeyid_backup
.map(|r| { .scan_prefix(prefix)
let (key, value) = r?; .map(|(key, value)| {
let mut parts = key.rsplit(|&b| b == 0xff); let mut parts = key.rsplit(|&b| b == 0xff);
let session_id = let session_id =
@@ -268,7 +273,7 @@ impl KeyBackups {
Ok::<_, Error>((session_id, key_data)) Ok::<_, Error>((session_id, key_data))
}) })
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.collect() .collect())
} }
pub fn get_session( pub fn get_session(
@@ -302,13 +307,8 @@ impl KeyBackups {
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
key.push(0xff); key.push(0xff);
for outdated_key in self for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
.backupkeyid_backup self.backupkeyid_backup.remove(&outdated_key)?;
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
} }
Ok(()) Ok(())
@@ -327,13 +327,8 @@ impl KeyBackups {
key.extend_from_slice(&room_id.as_bytes()); key.extend_from_slice(&room_id.as_bytes());
key.push(0xff); key.push(0xff);
for outdated_key in self for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
.backupkeyid_backup self.backupkeyid_backup.remove(&outdated_key)?;
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
} }
Ok(()) Ok(())
@@ -354,13 +349,8 @@ impl KeyBackups {
key.push(0xff); key.push(0xff);
key.extend_from_slice(&session_id.as_bytes()); key.extend_from_slice(&session_id.as_bytes());
for outdated_key in self for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
.backupkeyid_backup self.backupkeyid_backup.remove(&outdated_key)?;
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
} }
Ok(()) Ok(())
+95 -48
View File
@@ -1,25 +1,28 @@
use crate::database::globals::Globals;
use image::{imageops::FilterType, GenericImageView}; use image::{imageops::FilterType, GenericImageView};
use super::abstraction::Tree;
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use std::mem; use std::{mem, sync::Arc};
use tokio::{fs::File, io::AsyncReadExt, io::AsyncWriteExt};
pub struct FileMeta { pub struct FileMeta {
pub filename: Option<String>, pub content_disposition: Option<String>,
pub content_type: Option<String>, pub content_type: Option<String>,
pub file: Vec<u8>, pub file: Vec<u8>,
} }
#[derive(Clone)]
pub struct Media { pub struct Media {
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + WidthHeight + Filename + ContentType pub(super) mediaid_file: Arc<dyn Tree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
} }
impl Media { impl Media {
/// Uploads or replaces a file. /// Uploads a file.
pub fn create( pub async fn create(
&self, &self,
mxc: String, mxc: String,
filename: &Option<&str>, globals: &Globals,
content_disposition: &Option<&str>,
content_type: &Option<&str>, content_type: &Option<&str>,
file: &[u8], file: &[u8],
) -> Result<()> { ) -> Result<()> {
@@ -28,7 +31,12 @@ impl Media {
key.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail key.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
key.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail key.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
key.push(0xff); key.push(0xff);
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default()); key.extend_from_slice(
content_disposition
.as_ref()
.map(|f| f.as_bytes())
.unwrap_or_default(),
);
key.push(0xff); key.push(0xff);
key.extend_from_slice( key.extend_from_slice(
content_type content_type
@@ -37,16 +45,20 @@ impl Media {
.unwrap_or_default(), .unwrap_or_default(),
); );
self.mediaid_file.insert(key, file)?; let path = globals.get_media_file(&key);
let mut f = File::create(path).await?;
f.write_all(file).await?;
self.mediaid_file.insert(&key, &[])?;
Ok(()) Ok(())
} }
/// Uploads or replaces a file thumbnail. /// Uploads or replaces a file thumbnail.
pub fn upload_thumbnail( pub async fn upload_thumbnail(
&self, &self,
mxc: String, mxc: String,
filename: &Option<String>, globals: &Globals,
content_disposition: &Option<String>,
content_type: &Option<String>, content_type: &Option<String>,
width: u32, width: u32,
height: u32, height: u32,
@@ -57,7 +69,12 @@ impl Media {
key.extend_from_slice(&width.to_be_bytes()); key.extend_from_slice(&width.to_be_bytes());
key.extend_from_slice(&height.to_be_bytes()); key.extend_from_slice(&height.to_be_bytes());
key.push(0xff); key.push(0xff);
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default()); key.extend_from_slice(
content_disposition
.as_ref()
.map(|f| f.as_bytes())
.unwrap_or_default(),
);
key.push(0xff); key.push(0xff);
key.extend_from_slice( key.extend_from_slice(
content_type content_type
@@ -66,48 +83,59 @@ impl Media {
.unwrap_or_default(), .unwrap_or_default(),
); );
self.mediaid_file.insert(key, file)?; let path = globals.get_media_file(&key);
let mut f = File::create(path).await?;
f.write_all(file).await?;
self.mediaid_file.insert(&key, &[])?;
Ok(()) Ok(())
} }
/// Downloads a file. /// Downloads a file.
pub fn get(&self, mxc: &str) -> Result<Option<FileMeta>> { pub async fn get(&self, globals: &Globals, mxc: &str) -> Result<Option<FileMeta>> {
let mut prefix = mxc.as_bytes().to_vec(); let mut prefix = mxc.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
prefix.push(0xff); prefix.push(0xff);
if let Some(r) = self.mediaid_file.scan_prefix(&prefix).next() { let mut iter = self.mediaid_file.scan_prefix(prefix);
let (key, file) = r?; if let Some((key, _)) = iter.next() {
let path = globals.get_media_file(&key);
let mut file = Vec::new();
File::open(path).await?.read_to_end(&mut file).await?;
let mut parts = key.rsplit(|&b| b == 0xff); let mut parts = key.rsplit(|&b| b == 0xff);
let content_type = parts let content_type = parts
.next() .next()
.map(|bytes| { .map(|bytes| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| { utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.") Error::bad_database("Content type in mediaid_file is invalid unicode.")
})?) })
}) })
.transpose()?; .transpose()?;
let filename_bytes = parts let content_disposition_bytes = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let filename = if filename_bytes.is_empty() { let content_disposition = if content_disposition_bytes.is_empty() {
None None
} else { } else {
Some(utils::string_from_bytes(filename_bytes).map_err(|_| { Some(
Error::bad_database("Filename in mediaid_file is invalid unicode.") utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
})?) Error::bad_database(
"Content Disposition in mediaid_file is invalid unicode.",
)
})?,
)
}; };
Ok(Some(FileMeta { Ok(Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file: file.to_vec(), file,
})) }))
} else { } else {
Ok(None) Ok(None)
@@ -137,7 +165,13 @@ impl Media {
/// - Server creates the thumbnail and sends it to the user /// - Server creates the thumbnail and sends it to the user
/// ///
/// For width,height <= 96 the server uses another thumbnailing algorithm which crops the image afterwards. /// For width,height <= 96 the server uses another thumbnailing algorithm which crops the image afterwards.
pub fn get_thumbnail(&self, mxc: String, width: u32, height: u32) -> Result<Option<FileMeta>> { pub async fn get_thumbnail(
&self,
mxc: String,
globals: &Globals,
width: u32,
height: u32,
) -> Result<Option<FileMeta>> {
let (width, height, crop) = self let (width, height, crop) = self
.thumbnail_properties(width, height) .thumbnail_properties(width, height)
.unwrap_or((0, 0, false)); // 0, 0 because that's the original file .unwrap_or((0, 0, false)); // 0, 0 because that's the original file
@@ -155,63 +189,72 @@ impl Media {
original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
original_prefix.push(0xff); original_prefix.push(0xff);
if let Some(r) = self.mediaid_file.scan_prefix(&thumbnail_prefix).next() { if let Some((key, _)) = self.mediaid_file.scan_prefix(thumbnail_prefix).next() {
// Using saved thumbnail // Using saved thumbnail
let (key, file) = r?; let path = globals.get_media_file(&key);
let mut file = Vec::new();
File::open(path).await?.read_to_end(&mut file).await?;
let mut parts = key.rsplit(|&b| b == 0xff); let mut parts = key.rsplit(|&b| b == 0xff);
let content_type = parts let content_type = parts
.next() .next()
.map(|bytes| { .map(|bytes| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| { utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.") Error::bad_database("Content type in mediaid_file is invalid unicode.")
})?) })
}) })
.transpose()?; .transpose()?;
let filename_bytes = parts let content_disposition_bytes = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let filename = if filename_bytes.is_empty() { let content_disposition = if content_disposition_bytes.is_empty() {
None None
} else { } else {
Some( Some(
utils::string_from_bytes(filename_bytes) utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
.map_err(|_| Error::bad_database("Filename in db is invalid."))?, Error::bad_database("Content Disposition in db is invalid.")
})?,
) )
}; };
Ok(Some(FileMeta { Ok(Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file: file.to_vec(), file: file.to_vec(),
})) }))
} else if let Some(r) = self.mediaid_file.scan_prefix(&original_prefix).next() { } else if let Some((key, _)) = self.mediaid_file.scan_prefix(original_prefix).next() {
// Generate a thumbnail // Generate a thumbnail
let path = globals.get_media_file(&key);
let mut file = Vec::new();
File::open(path).await?.read_to_end(&mut file).await?;
let (key, file) = r?;
let mut parts = key.rsplit(|&b| b == 0xff); let mut parts = key.rsplit(|&b| b == 0xff);
let content_type = parts let content_type = parts
.next() .next()
.map(|bytes| { .map(|bytes| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| { utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.") Error::bad_database("Content type in mediaid_file is invalid unicode.")
})?) })
}) })
.transpose()?; .transpose()?;
let filename_bytes = parts let content_disposition_bytes = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let filename = if filename_bytes.is_empty() { let content_disposition = if content_disposition_bytes.is_empty() {
None None
} else { } else {
Some(utils::string_from_bytes(filename_bytes).map_err(|_| { Some(
Error::bad_database("Filename in mediaid_file is invalid unicode.") utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
})?) Error::bad_database(
"Content Disposition in mediaid_file is invalid unicode.",
)
})?,
)
}; };
if let Ok(image) = image::load_from_memory(&file) { if let Ok(image) = image::load_from_memory(&file) {
@@ -219,7 +262,7 @@ impl Media {
let original_height = image.height(); let original_height = image.height();
if width > original_width || height > original_height { if width > original_width || height > original_height {
return Ok(Some(FileMeta { return Ok(Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file: file.to_vec(), file: file.to_vec(),
})); }));
@@ -283,17 +326,21 @@ impl Media {
widthheight, widthheight,
); );
self.mediaid_file.insert(thumbnail_key, &*thumbnail_bytes)?; let path = globals.get_media_file(&thumbnail_key);
let mut f = File::create(path).await?;
f.write_all(&thumbnail_bytes).await?;
self.mediaid_file.insert(&thumbnail_key, &[])?;
Ok(Some(FileMeta { Ok(Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file: thumbnail_bytes.to_vec(), file: thumbnail_bytes.to_vec(),
})) }))
} else { } else {
// Couldn't parse file to generate thumbnail, send original // Couldn't parse file to generate thumbnail, send original
Ok(Some(FileMeta { Ok(Some(FileMeta {
filename, content_disposition,
content_type, content_type,
file: file.to_vec(), file: file.to_vec(),
})) }))
+36 -44
View File
@@ -14,23 +14,17 @@ use ruma::{
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak}, push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
uint, UInt, UserId, uint, UInt, UserId,
}; };
use sled::IVec;
use std::{convert::TryFrom, fmt::Debug}; use std::{convert::TryFrom, fmt::Debug, mem, sync::Arc};
use super::abstraction::Tree;
#[derive(Debug, Clone)]
pub struct PushData { pub struct PushData {
/// UserId + pushkey -> Pusher /// UserId + pushkey -> Pusher
pub(super) senderkey_pusher: sled::Tree, pub(super) senderkey_pusher: Arc<dyn Tree>,
} }
impl PushData { impl PushData {
pub fn new(db: &sled::Db) -> Result<Self> {
Ok(Self {
senderkey_pusher: db.open_tree("senderkey_pusher")?,
})
}
pub fn set_pusher(&self, sender: &UserId, pusher: set_pusher::Pusher) -> Result<()> { pub fn set_pusher(&self, sender: &UserId, pusher: set_pusher::Pusher) -> Result<()> {
let mut key = sender.as_bytes().to_vec(); let mut key = sender.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
@@ -40,14 +34,14 @@ impl PushData {
if pusher.kind.is_none() { if pusher.kind.is_none() {
return self return self
.senderkey_pusher .senderkey_pusher
.remove(key) .remove(&key)
.map(|_| ()) .map(|_| ())
.map_err(Into::into); .map_err(Into::into);
} }
self.senderkey_pusher.insert( self.senderkey_pusher.insert(
key, &key,
&*serde_json::to_string(&pusher).expect("Pusher is valid JSON string"), &serde_json::to_vec(&pusher).expect("Pusher is valid JSON value"),
)?; )?;
Ok(()) Ok(())
@@ -57,8 +51,8 @@ impl PushData {
self.senderkey_pusher self.senderkey_pusher
.get(senderkey)? .get(senderkey)?
.map(|push| { .map(|push| {
Ok(serde_json::from_slice(&*push) serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?) .map_err(|_| Error::bad_database("Invalid Pusher in db."))
}) })
.transpose() .transpose()
} }
@@ -69,23 +63,21 @@ impl PushData {
self.senderkey_pusher self.senderkey_pusher
.scan_prefix(prefix) .scan_prefix(prefix)
.values() .map(|(_, push)| {
.map(|push| { serde_json::from_slice(&*push)
let push = push.map_err(|_| Error::bad_database("Invalid push bytes in db."))?; .map_err(|_| Error::bad_database("Invalid Pusher in db."))
Ok(serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
}) })
.collect() .collect()
} }
pub fn get_pusher_senderkeys(&self, sender: &UserId) -> impl Iterator<Item = Result<IVec>> { pub fn get_pusher_senderkeys<'a>(
&'a self,
sender: &UserId,
) -> impl Iterator<Item = Box<[u8]>> + 'a {
let mut prefix = sender.as_bytes().to_vec(); let mut prefix = sender.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
self.senderkey_pusher self.senderkey_pusher.scan_prefix(prefix).map(|(k, _)| k)
.scan_prefix(prefix)
.keys()
.map(|r| Ok(r?))
} }
} }
@@ -114,24 +106,23 @@ where
//*reqwest_request.timeout_mut() = Some(Duration::from_secs(5)); //*reqwest_request.timeout_mut() = Some(Duration::from_secs(5));
let url = reqwest_request.url().clone(); let url = reqwest_request.url().clone();
let reqwest_response = globals.reqwest_client().execute(reqwest_request).await; let response = globals.reqwest_client().execute(reqwest_request).await;
// Because reqwest::Response -> http::Response is complicated: match response {
match reqwest_response { Ok(mut response) => {
Ok(mut reqwest_response) => { // reqwest::Response -> http::Response conversion
let status = reqwest_response.status(); let status = response.status();
let mut http_response = http::Response::builder().status(status); let mut http_response_builder = http::Response::builder()
let headers = http_response.headers_mut().unwrap(); .status(status)
.version(response.version());
mem::swap(
response.headers_mut(),
http_response_builder
.headers_mut()
.expect("http::response::Builder is usable"),
);
for (k, v) in reqwest_response.headers_mut().drain() { let body = response.bytes().await.unwrap_or_else(|e| {
if let Some(key) = k {
headers.insert(key, v);
}
}
let status = reqwest_response.status();
let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
warn!("server error {}", e); warn!("server error {}", e);
Vec::new().into() Vec::new().into()
}); // TODO: handle timeout }); // TODO: handle timeout
@@ -147,7 +138,7 @@ where
} }
let response = T::IncomingResponse::try_from_http_response( let response = T::IncomingResponse::try_from_http_response(
http_response http_response_builder
.body(body) .body(body)
.expect("reqwest body is valid http body"), .expect("reqwest body is valid http body"),
); );
@@ -212,7 +203,7 @@ pub fn get_actions<'a>(
.rooms .rooms
.room_state_get(&pdu.room_id, &EventType::RoomPowerLevels, "")? .room_state_get(&pdu.room_id, &EventType::RoomPowerLevels, "")?
.map(|ev| { .map(|ev| {
serde_json::from_value(ev.content) serde_json::from_value(ev.content.clone())
.map_err(|_| Error::bad_database("invalid m.room.power_levels event")) .map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
}) })
.transpose()? .transpose()?
@@ -295,7 +286,8 @@ async fn send_notice(
} else { } else {
notifi.sender = Some(&event.sender); notifi.sender = Some(&event.sender);
notifi.event_type = Some(&event.kind); notifi.event_type = Some(&event.kind);
notifi.content = serde_json::value::to_raw_value(&event.content).ok(); let content = serde_json::value::to_raw_value(&event.content).ok();
notifi.content = content.as_deref();
if event.kind == EventType::RoomMember { if event.kind == EventType::RoomMember {
notifi.user_is_target = event.state_key.as_deref() == Some(event.sender.as_str()); notifi.user_is_target = event.state_key.as_deref() == Some(event.sender.as_str());
+376 -349
View File
File diff suppressed because it is too large Load Diff
+120 -64
View File
@@ -1,28 +1,29 @@
use crate::{utils, Error, Result}; use crate::{database::abstraction::Tree, utils, Error, Result};
use ruma::{ use ruma::{
events::{ events::{
presence::{PresenceEvent, PresenceEventContent}, presence::{PresenceEvent, PresenceEventContent},
AnyEvent as EduEvent, SyncEphemeralRoomEvent, AnyEphemeralRoomEvent, SyncEphemeralRoomEvent,
}, },
presence::PresenceState, presence::PresenceState,
serde::Raw, serde::Raw,
signatures::CanonicalJsonObject,
RoomId, UInt, UserId, RoomId, UInt, UserId,
}; };
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
mem, mem,
sync::Arc,
}; };
#[derive(Clone)]
pub struct RoomEdus { pub struct RoomEdus {
pub(in super::super) readreceiptid_readreceipt: sled::Tree, // ReadReceiptId = RoomId + Count + UserId pub(in super::super) readreceiptid_readreceipt: Arc<dyn Tree>, // ReadReceiptId = RoomId + Count + UserId
pub(in super::super) roomuserid_privateread: sled::Tree, // RoomUserId = Room + User, PrivateRead = Count pub(in super::super) roomuserid_privateread: Arc<dyn Tree>, // RoomUserId = Room + User, PrivateRead = Count
pub(in super::super) roomuserid_lastprivatereadupdate: sled::Tree, // LastPrivateReadUpdate = Count pub(in super::super) roomuserid_lastprivatereadupdate: Arc<dyn Tree>, // LastPrivateReadUpdate = Count
pub(in super::super) typingid_userid: sled::Tree, // TypingId = RoomId + TimeoutTime + Count pub(in super::super) typingid_userid: Arc<dyn Tree>, // TypingId = RoomId + TimeoutTime + Count
pub(in super::super) roomid_lasttypingupdate: sled::Tree, // LastRoomTypingUpdate = Count pub(in super::super) roomid_lasttypingupdate: Arc<dyn Tree>, // LastRoomTypingUpdate = Count
pub(in super::super) presenceid_presence: sled::Tree, // PresenceId = RoomId + Count + UserId pub(in super::super) presenceid_presence: Arc<dyn Tree>, // PresenceId = RoomId + Count + UserId
pub(in super::super) userid_lastpresenceupdate: sled::Tree, // LastPresenceUpdate = Count pub(in super::super) userid_lastpresenceupdate: Arc<dyn Tree>, // LastPresenceUpdate = Count
} }
impl RoomEdus { impl RoomEdus {
@@ -31,21 +32,21 @@ impl RoomEdus {
&self, &self,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,
event: EduEvent, event: AnyEphemeralRoomEvent,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut prefix = room_id.as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut last_possible_key = prefix.clone();
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
// Remove old entry // Remove old entry
if let Some(old) = self if let Some((old, _)) = self
.readreceiptid_readreceipt .readreceiptid_readreceipt
.scan_prefix(&prefix) .iter_from(&last_possible_key, true)
.keys() .take_while(|(key, _)| key.starts_with(&prefix))
.rev() .find(|(key, _)| {
.filter_map(|r| r.ok())
.take_while(|key| key.starts_with(&prefix))
.find(|key| {
key.rsplit(|&b| b == 0xff) key.rsplit(|&b| b == 0xff)
.next() .next()
.expect("rsplit always returns an element") .expect("rsplit always returns an element")
@@ -53,7 +54,7 @@ impl RoomEdus {
}) })
{ {
// This is the old room_latest // This is the old room_latest
self.readreceiptid_readreceipt.remove(old)?; self.readreceiptid_readreceipt.remove(&old)?;
} }
let mut room_latest_id = prefix; let mut room_latest_id = prefix;
@@ -62,8 +63,8 @@ impl RoomEdus {
room_latest_id.extend_from_slice(&user_id.as_bytes()); room_latest_id.extend_from_slice(&user_id.as_bytes());
self.readreceiptid_readreceipt.insert( self.readreceiptid_readreceipt.insert(
room_latest_id, &room_latest_id,
&*serde_json::to_string(&event).expect("EduEvent::to_string always works"), &serde_json::to_vec(&event).expect("EduEvent::to_string always works"),
)?; )?;
Ok(()) Ok(())
@@ -71,27 +72,47 @@ impl RoomEdus {
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`. /// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn readreceipts_since( pub fn readreceipts_since<'a>(
&self, &'a self,
room_id: &RoomId, room_id: &RoomId,
since: u64, since: u64,
) -> Result<impl Iterator<Item = Result<Raw<ruma::events::AnySyncEphemeralRoomEvent>>>> { ) -> impl Iterator<Item = Result<(UserId, u64, Raw<ruma::events::AnySyncEphemeralRoomEvent>)>> + 'a
{
let mut prefix = room_id.as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let prefix2 = prefix.clone();
let mut first_possible_edu = prefix.clone(); let mut first_possible_edu = prefix.clone();
first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since
Ok(self self.readreceiptid_readreceipt
.readreceiptid_readreceipt .iter_from(&first_possible_edu, false)
.range(&*first_possible_edu..) .take_while(move |(k, _)| k.starts_with(&prefix2))
.filter_map(|r| r.ok()) .map(move |(k, v)| {
.take_while(move |(k, _)| k.starts_with(&prefix)) let count =
.map(|(_, v)| { utils::u64_from_bytes(&k[prefix.len()..prefix.len() + mem::size_of::<u64>()])
Ok(serde_json::from_slice(&v).map_err(|_| { .map_err(|_| Error::bad_database("Invalid readreceiptid count in db."))?;
Error::bad_database("Read receipt in roomlatestid_roomlatest is invalid.") let user_id = UserId::try_from(
})?) utils::string_from_bytes(&k[prefix.len() + mem::size_of::<u64>() + 1..])
})) .map_err(|_| {
Error::bad_database("Invalid readreceiptid userid bytes in db.")
})?,
)
.map_err(|_| Error::bad_database("Invalid readreceiptid userid in db."))?;
let mut json = serde_json::from_slice::<CanonicalJsonObject>(&v).map_err(|_| {
Error::bad_database("Read receipt in roomlatestid_roomlatest is invalid json.")
})?;
json.remove("room_id");
Ok((
user_id,
count,
Raw::from_json(
serde_json::value::to_raw_value(&json).expect("json is valid raw value"),
),
))
})
} }
/// Sets a private read marker at `count`. /// Sets a private read marker at `count`.
@@ -122,11 +143,13 @@ impl RoomEdus {
key.push(0xff); key.push(0xff);
key.extend_from_slice(&user_id.as_bytes()); key.extend_from_slice(&user_id.as_bytes());
self.roomuserid_privateread.get(key)?.map_or(Ok(None), |v| { self.roomuserid_privateread
Ok(Some(utils::u64_from_bytes(&v).map_err(|_| { .get(&key)?
Error::bad_database("Invalid private read marker bytes") .map_or(Ok(None), |v| {
})?)) Ok(Some(utils::u64_from_bytes(&v).map_err(|_| {
}) Error::bad_database("Invalid private read marker bytes")
})?))
})
} }
/// Returns the count of the last typing update in this room. /// Returns the count of the last typing update in this room.
@@ -191,11 +214,10 @@ impl RoomEdus {
// Maybe there are multiple ones from calling roomtyping_add multiple times // Maybe there are multiple ones from calling roomtyping_add multiple times
for outdated_edu in self for outdated_edu in self
.typingid_userid .typingid_userid
.scan_prefix(&prefix) .scan_prefix(prefix)
.filter_map(|r| r.ok()) .filter(|(_, v)| &**v == user_id.as_bytes())
.filter(|(_, v)| v == user_id.as_bytes())
{ {
self.typingid_userid.remove(outdated_edu.0)?; self.typingid_userid.remove(&outdated_edu.0)?;
found_outdated = true; found_outdated = true;
} }
@@ -223,10 +245,8 @@ impl RoomEdus {
// Find all outdated edus before inserting a new one // Find all outdated edus before inserting a new one
for outdated_edu in self for outdated_edu in self
.typingid_userid .typingid_userid
.scan_prefix(&prefix) .scan_prefix(prefix)
.keys() .map(|(key, _)| {
.map(|key| {
let key = key?;
Ok::<_, Error>(( Ok::<_, Error>((
key.clone(), key.clone(),
utils::u64_from_bytes( utils::u64_from_bytes(
@@ -241,7 +261,7 @@ impl RoomEdus {
.take_while(|&(_, timestamp)| timestamp < current_timestamp) .take_while(|&(_, timestamp)| timestamp < current_timestamp)
{ {
// This is an outdated edu (time > timestamp) // This is an outdated edu (time > timestamp)
self.typingid_userid.remove(outdated_edu.0)?; self.typingid_userid.remove(&outdated_edu.0)?;
found_outdated = true; found_outdated = true;
} }
@@ -285,14 +305,11 @@ impl RoomEdus {
for user_id in self for user_id in self
.typingid_userid .typingid_userid
.scan_prefix(prefix) .scan_prefix(prefix)
.values() .map(|(_, user_id)| {
.map(|user_id| { UserId::try_from(utils::string_from_bytes(&user_id).map_err(|_| {
Ok::<_, Error>( Error::bad_database("User ID in typingid_userid is invalid unicode.")
UserId::try_from(utils::string_from_bytes(&user_id?).map_err(|_| { })?)
Error::bad_database("User ID in typingid_userid is invalid unicode.") .map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))
})?)
.map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))?,
)
}) })
{ {
user_ids.insert(user_id?); user_ids.insert(user_id?);
@@ -327,12 +344,12 @@ impl RoomEdus {
presence_id.extend_from_slice(&presence.sender.as_bytes()); presence_id.extend_from_slice(&presence.sender.as_bytes());
self.presenceid_presence.insert( self.presenceid_presence.insert(
presence_id, &presence_id,
&*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"), &serde_json::to_vec(&presence).expect("PresenceEvent can be serialized"),
)?; )?;
self.userid_lastpresenceupdate.insert( self.userid_lastpresenceupdate.insert(
&user_id.as_bytes(), user_id.as_bytes(),
&utils::millis_since_unix_epoch().to_be_bytes(), &utils::millis_since_unix_epoch().to_be_bytes(),
)?; )?;
@@ -362,6 +379,47 @@ impl RoomEdus {
.transpose() .transpose()
} }
pub fn get_last_presence_event(
&self,
user_id: &UserId,
room_id: &RoomId,
) -> Result<Option<PresenceEvent>> {
let last_update = match self.last_presence_update(user_id)? {
Some(last) => last,
None => return Ok(None),
};
let mut presence_id = room_id.as_bytes().to_vec();
presence_id.push(0xff);
presence_id.extend_from_slice(&last_update.to_be_bytes());
presence_id.push(0xff);
presence_id.extend_from_slice(&user_id.as_bytes());
self.presenceid_presence
.get(&presence_id)?
.map(|value| {
let mut presence = serde_json::from_slice::<PresenceEvent>(&value)
.map_err(|_| Error::bad_database("Invalid presence event in db."))?;
let current_timestamp: UInt = utils::millis_since_unix_epoch()
.try_into()
.expect("time is valid");
if presence.content.presence == PresenceState::Online {
// Don't set last_active_ago when the user is online
presence.content.last_active_ago = None;
} else {
// Convert from timestamp to duration
presence.content.last_active_ago = presence
.content
.last_active_ago
.map(|timestamp| current_timestamp - timestamp);
}
Ok(presence)
})
.transpose()
}
/// Sets all users to offline who have been quiet for too long. /// Sets all users to offline who have been quiet for too long.
pub fn presence_maintain( pub fn presence_maintain(
&self, &self,
@@ -373,7 +431,6 @@ impl RoomEdus {
for (user_id_bytes, last_timestamp) in self for (user_id_bytes, last_timestamp) in self
.userid_lastpresenceupdate .userid_lastpresenceupdate
.iter() .iter()
.filter_map(|r| r.ok())
.filter_map(|(k, bytes)| { .filter_map(|(k, bytes)| {
Some(( Some((
k, k,
@@ -403,8 +460,8 @@ impl RoomEdus {
presence_id.extend_from_slice(&user_id_bytes); presence_id.extend_from_slice(&user_id_bytes);
self.presenceid_presence.insert( self.presenceid_presence.insert(
presence_id, &presence_id,
&*serde_json::to_string(&PresenceEvent { &serde_json::to_vec(&PresenceEvent {
content: PresenceEventContent { content: PresenceEventContent {
avatar_url: None, avatar_url: None,
currently_active: None, currently_active: None,
@@ -450,8 +507,7 @@ impl RoomEdus {
for (key, value) in self for (key, value) in self
.presenceid_presence .presenceid_presence
.range(&*first_possible_edu..) .iter_from(&*first_possible_edu, false)
.filter_map(|r| r.ok())
.take_while(|(key, _)| key.starts_with(&prefix)) .take_while(|(key, _)| key.starts_with(&prefix))
{ {
let user_id = UserId::try_from( let user_id = UserId::try_from(
+366 -271
View File
@@ -1,9 +1,9 @@
use std::{ use std::{
collections::HashMap, collections::{BTreeMap, HashMap},
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt::Debug, fmt::Debug,
sync::Arc, sync::Arc,
time::{Duration, Instant, SystemTime}, time::{Duration, Instant},
}; };
use crate::{ use crate::{
@@ -12,15 +12,28 @@ use crate::{
use federation::transactions::send_transaction_message; use federation::transactions::send_transaction_message;
use log::{error, warn}; use log::{error, warn};
use ring::digest; use ring::digest;
use rocket::futures::stream::{FuturesUnordered, StreamExt}; use rocket::futures::{
channel::mpsc,
stream::{FuturesUnordered, StreamExt},
};
use ruma::{ use ruma::{
api::{appservice, federation, OutgoingRequest}, api::{
events::{push_rules, EventType}, appservice,
push, ServerName, UInt, UserId, federation::{
self,
transactions::edu::{Edu, ReceiptContent, ReceiptData, ReceiptMap},
},
OutgoingRequest,
},
events::{push_rules, AnySyncEphemeralRoomEvent, EventType},
push,
receipt::ReceiptType,
MilliSecondsSinceUnixEpoch, ServerName, UInt, UserId,
}; };
use sled::IVec;
use tokio::{select, sync::Semaphore}; use tokio::{select, sync::Semaphore};
use super::abstraction::Tree;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OutgoingKind { pub enum OutgoingKind {
Appservice(Box<ServerName>), Appservice(Box<ServerName>),
@@ -28,12 +41,46 @@ pub enum OutgoingKind {
Normal(Box<ServerName>), Normal(Box<ServerName>),
} }
#[derive(Clone)] impl OutgoingKind {
pub fn get_prefix(&self) -> Vec<u8> {
let mut prefix = match self {
OutgoingKind::Appservice(server) => {
let mut p = b"+".to_vec();
p.extend_from_slice(server.as_bytes());
p
}
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
}
OutgoingKind::Normal(server) => {
let mut p = Vec::new();
p.extend_from_slice(server.as_bytes());
p
}
};
prefix.push(0xff);
prefix
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SendingEventType {
Pdu(Vec<u8>),
Edu(Vec<u8>),
}
pub struct Sending { pub struct Sending {
/// The state for a given state hash. /// The state for a given state hash.
pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId pub(super) servername_educount: Arc<dyn Tree>, // EduCount: Count of last EDU sync
pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+ / $)ServerName / UserId + PduId pub(super) servernamepduids: Arc<dyn Tree>, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
pub(super) servercurrentevents: Arc<dyn Tree>, // ServerCurrentEvents = (+ / $)ServerName / UserId + PduId / (*)EduEvent
pub(super) maximum_requests: Arc<Semaphore>, pub(super) maximum_requests: Arc<Semaphore>,
pub sender: mpsc::UnboundedSender<Vec<u8>>,
} }
enum TransactionStatus { enum TransactionStatus {
@@ -43,28 +90,23 @@ enum TransactionStatus {
} }
impl Sending { impl Sending {
pub fn start_handler(&self, db: &Database) { pub fn start_handler(&self, db: Arc<Database>, mut receiver: mpsc::UnboundedReceiver<Vec<u8>>) {
let servernamepduids = self.servernamepduids.clone();
let servercurrentpdus = self.servercurrentpdus.clone();
let db = db.clone();
tokio::spawn(async move { tokio::spawn(async move {
let mut futures = FuturesUnordered::new(); let mut futures = FuturesUnordered::new();
// Retry requests we could not finish yet
let mut subscriber = servernamepduids.watch_prefix(b"");
let mut current_transaction_status = HashMap::<Vec<u8>, TransactionStatus>::new(); let mut current_transaction_status = HashMap::<Vec<u8>, TransactionStatus>::new();
let mut initial_transactions = HashMap::<OutgoingKind, Vec<Vec<u8>>>::new(); // Retry requests we could not finish yet
for (key, outgoing_kind, pdu) in servercurrentpdus let mut initial_transactions = HashMap::<OutgoingKind, Vec<SendingEventType>>::new();
.iter() for (key, outgoing_kind, event) in
.filter_map(|r| r.ok()) db.sending
.filter_map(|(key, _)| { .servercurrentevents
Self::parse_servercurrentpdus(&key) .iter()
.ok() .filter_map(|(key, _)| {
.map(|(k, p)| (key, k, p.to_vec())) Self::parse_servercurrentevent(&key)
}) .ok()
.map(|(k, e)| (key, k, e))
})
{ {
let entry = initial_transactions let entry = initial_transactions
.entry(outgoing_kind.clone()) .entry(outgoing_kind.clone())
@@ -72,39 +114,20 @@ impl Sending {
if entry.len() > 30 { if entry.len() > 30 {
warn!( warn!(
"Dropping some current pdu: {:?} {:?} {:?}", "Dropping some current events: {:?} {:?} {:?}",
key, outgoing_kind, pdu key, outgoing_kind, event
); );
servercurrentpdus.remove(key).unwrap(); db.sending.servercurrentevents.remove(&key).unwrap();
continue; continue;
} }
entry.push(pdu); entry.push(event);
} }
for (outgoing_kind, pdus) in initial_transactions { for (outgoing_kind, events) in initial_transactions {
let mut prefix = match &outgoing_kind { current_transaction_status
OutgoingKind::Appservice(server) => { .insert(outgoing_kind.get_prefix(), TransactionStatus::Running);
let mut p = b"+".to_vec(); futures.push(Self::handle_events(outgoing_kind.clone(), events, &db));
p.extend_from_slice(server.as_bytes());
p
}
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
}
OutgoingKind::Normal(server) => {
let mut p = Vec::new();
p.extend_from_slice(server.as_bytes());
p
}
};
prefix.push(0xff);
current_transaction_status.insert(prefix, TransactionStatus::Running);
futures.push(Self::handle_event(outgoing_kind.clone(), pdus, &db));
} }
loop { loop {
@@ -112,58 +135,42 @@ impl Sending {
Some(response) = futures.next() => { Some(response) = futures.next() => {
match response { match response {
Ok(outgoing_kind) => { Ok(outgoing_kind) => {
let mut prefix = match &outgoing_kind { let prefix = outgoing_kind.get_prefix();
OutgoingKind::Appservice(server) => { for (key, _) in db.sending.servercurrentevents
let mut p = b"+".to_vec(); .scan_prefix(prefix.clone())
p.extend_from_slice(server.as_bytes());
p
}
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(server) => {
let mut p = vec![];
p.extend_from_slice(server.as_bytes());
p
},
};
prefix.push(0xff);
for key in servercurrentpdus
.scan_prefix(&prefix)
.keys()
.filter_map(|r| r.ok())
{ {
servercurrentpdus.remove(key).unwrap(); db.sending.servercurrentevents.remove(&key).unwrap();
} }
// Find events that have been added since starting the last request // Find events that have been added since starting the last request
let new_pdus = servernamepduids let new_events = db.sending.servernamepduids
.scan_prefix(&prefix) .scan_prefix(prefix.clone())
.keys() .map(|(k, _)| {
.filter_map(|r| r.ok()) SendingEventType::Pdu(k[prefix.len()..].to_vec())
.map(|k| {
k[prefix.len()..].to_vec()
}) })
.take(30) .take(30)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !new_pdus.is_empty() { // TODO: find edus
for pdu_id in &new_pdus {
if !new_events.is_empty() {
// Insert pdus we found
for event in &new_events {
let mut current_key = prefix.clone(); let mut current_key = prefix.clone();
current_key.extend_from_slice(pdu_id); match event {
servercurrentpdus.insert(&current_key, &[]).unwrap(); SendingEventType::Pdu(b) |
servernamepduids.remove(&current_key).unwrap(); SendingEventType::Edu(b) => {
current_key.extend_from_slice(&b);
db.sending.servercurrentevents.insert(&current_key, &[]).unwrap();
db.sending.servernamepduids.remove(&current_key).unwrap();
}
}
} }
futures.push( futures.push(
Self::handle_event( Self::handle_events(
outgoing_kind.clone(), outgoing_kind.clone(),
new_pdus, new_events,
&db, &db,
) )
); );
@@ -172,29 +179,7 @@ impl Sending {
} }
} }
Err((outgoing_kind, _)) => { Err((outgoing_kind, _)) => {
let mut prefix = match &outgoing_kind { current_transaction_status.entry(outgoing_kind.get_prefix()).and_modify(|e| *e = match e {
OutgoingKind::Appservice(serv) => {
let mut p = b"+".to_vec();
p.extend_from_slice(serv.as_bytes());
p
},
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(serv) => {
let mut p = vec![];
p.extend_from_slice(serv.as_bytes());
p
},
};
prefix.push(0xff);
current_transaction_status.entry(prefix).and_modify(|e| *e = match e {
TransactionStatus::Running => TransactionStatus::Failed(1, Instant::now()), TransactionStatus::Running => TransactionStatus::Failed(1, Instant::now()),
TransactionStatus::Retrying(n) => TransactionStatus::Failed(*n+1, Instant::now()), TransactionStatus::Retrying(n) => TransactionStatus::Failed(*n+1, Instant::now()),
TransactionStatus::Failed(_, _) => { TransactionStatus::Failed(_, _) => {
@@ -205,98 +190,15 @@ impl Sending {
} }
}; };
}, },
Some(event) = &mut subscriber => { Some(key) = receiver.next() => {
if let sled::Event::Insert { key, .. } = event { if let Ok((outgoing_kind, event)) = Self::parse_servercurrentevent(&key) {
// New sled version: if let Ok(Some(events)) = Self::select_events(
//for (_tree, key, value_opt) in &event { &outgoing_kind,
// if value_opt.is_none() { vec![(event, key)],
// continue; &mut current_transaction_status,
// } &db
) {
let servernamepduid = key.clone(); futures.push(Self::handle_events(outgoing_kind, events, &db));
let mut retry = false;
if let Some((outgoing_kind, prefix, pdu_id)) = Self::parse_servercurrentpdus(&servernamepduid)
.ok()
.map(|(outgoing_kind, pdu_id)| {
let mut prefix = match &outgoing_kind {
OutgoingKind::Appservice(serv) => {
let mut p = b"+".to_vec();
p.extend_from_slice(serv.as_bytes());
p
},
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(serv) => {
let mut p = vec![];
p.extend_from_slice(serv.as_bytes());
p
},
};
prefix.push(0xff);
(outgoing_kind, prefix, pdu_id)
})
.filter(|(_, prefix, _)| {
let entry = current_transaction_status.entry(prefix.clone());
let mut allow = true;
entry.and_modify(|e| match e {
TransactionStatus::Running | TransactionStatus::Retrying(_) => {
allow = false; // already running
},
TransactionStatus::Failed(tries, time) => {
// Fail if a request has failed recently (exponential backoff)
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60*60*24) {
min_elapsed_duration = Duration::from_secs(60*60*24);
}
if time.elapsed() < min_elapsed_duration {
allow = false;
} else {
retry = true;
*e = TransactionStatus::Retrying(*tries);
}
}
}).or_insert(TransactionStatus::Running);
allow
})
{
let mut pdus = Vec::new();
if retry {
// We retry the previous transaction
for pdu in servercurrentpdus
.scan_prefix(&prefix)
.filter_map(|r| r.ok())
.filter_map(|(key, _)| {
Self::parse_servercurrentpdus(&key)
.ok()
.map(|(_, p)| p.to_vec())
})
{
pdus.push(pdu);
}
} else {
servercurrentpdus.insert(&key, &[]).unwrap();
servernamepduids.remove(&key).unwrap();
pdus.push(pdu_id.to_vec());
}
futures.push(
Self::handle_event(
outgoing_kind,
pdus,
&db,
)
);
} }
} }
} }
@@ -305,13 +207,163 @@ impl Sending {
}); });
} }
fn select_events(
outgoing_kind: &OutgoingKind,
new_events: Vec<(SendingEventType, Vec<u8>)>, // Events we want to send: event and full key
current_transaction_status: &mut HashMap<Vec<u8>, TransactionStatus>,
db: &Database,
) -> Result<Option<Vec<SendingEventType>>> {
let mut retry = false;
let mut allow = true;
let prefix = outgoing_kind.get_prefix();
let entry = current_transaction_status.entry(prefix.clone());
entry
.and_modify(|e| match e {
TransactionStatus::Running | TransactionStatus::Retrying(_) => {
allow = false; // already running
}
TransactionStatus::Failed(tries, time) => {
// Fail if a request has failed recently (exponential backoff)
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
if time.elapsed() < min_elapsed_duration {
allow = false;
} else {
retry = true;
*e = TransactionStatus::Retrying(*tries);
}
}
})
.or_insert(TransactionStatus::Running);
if !allow {
return Ok(None);
}
let mut events = Vec::new();
if retry {
// We retry the previous transaction
for (key, _) in db.sending.servercurrentevents.scan_prefix(prefix) {
if let Ok((_, e)) = Self::parse_servercurrentevent(&key) {
events.push(e);
}
}
} else {
for (e, full_key) in new_events {
db.sending.servercurrentevents.insert(&full_key, &[])?;
// If it was a PDU we have to unqueue it
// TODO: don't try to unqueue EDUs
db.sending.servernamepduids.remove(&full_key)?;
events.push(e);
}
if let OutgoingKind::Normal(server_name) = outgoing_kind {
if let Ok((select_edus, last_count)) = Self::select_edus(db, server_name) {
events.extend_from_slice(&select_edus);
db.sending
.servername_educount
.insert(server_name.as_bytes(), &last_count.to_be_bytes())?;
}
}
}
Ok(Some(events))
}
pub fn select_edus(db: &Database, server: &ServerName) -> Result<(Vec<SendingEventType>, u64)> {
// u64: count of last edu
let since = db
.sending
.servername_educount
.get(server.as_bytes())?
.map_or(Ok(0), |bytes| {
utils::u64_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Invalid u64 in servername_educount."))
})?;
let mut events = Vec::new();
let mut max_edu_count = since;
'outer: for room_id in db.rooms.server_rooms(server) {
let room_id = room_id?;
for r in db.rooms.edus.readreceipts_since(&room_id, since) {
let (user_id, count, read_receipt) = r?;
if count > max_edu_count {
max_edu_count = count;
}
if user_id.server_name() != db.globals.server_name() {
continue;
}
let event =
serde_json::from_str::<AnySyncEphemeralRoomEvent>(&read_receipt.json().get())
.map_err(|_| Error::bad_database("Invalid edu event in read_receipts."))?;
let federation_event = match event {
AnySyncEphemeralRoomEvent::Receipt(r) => {
let mut read = BTreeMap::new();
let (event_id, mut receipt) = r
.content
.0
.into_iter()
.next()
.expect("we only use one event per read receipt");
let receipt = receipt
.remove(&ReceiptType::Read)
.expect("our read receipts always set this")
.remove(&user_id)
.expect("our read receipts always have the user here");
read.insert(
user_id,
ReceiptData {
data: receipt.clone(),
event_ids: vec![event_id.clone()],
},
);
let receipt_map = ReceiptMap { read };
let mut receipts = BTreeMap::new();
receipts.insert(room_id.clone(), receipt_map);
Edu::Receipt(ReceiptContent { receipts })
}
_ => {
Error::bad_database("Invalid event type in read_receipts");
continue;
}
};
events.push(SendingEventType::Edu(
serde_json::to_vec(&federation_event).expect("json can be serialized"),
));
if events.len() >= 20 {
break 'outer;
}
}
}
Ok((events, max_edu_count))
}
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: IVec) -> Result<()> { pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: Box<[u8]>) -> Result<()> {
let mut key = b"$".to_vec(); let mut key = b"$".to_vec();
key.extend_from_slice(&senderkey); key.extend_from_slice(&senderkey);
key.push(0xff); key.push(0xff);
key.extend_from_slice(pdu_id); key.extend_from_slice(pdu_id);
self.servernamepduids.insert(key, b"")?; self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
Ok(()) Ok(())
} }
@@ -321,7 +373,8 @@ impl Sending {
let mut key = server.as_bytes().to_vec(); let mut key = server.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(pdu_id); key.extend_from_slice(pdu_id);
self.servernamepduids.insert(key, b"")?; self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
Ok(()) Ok(())
} }
@@ -332,13 +385,14 @@ impl Sending {
key.extend_from_slice(appservice_id.as_bytes()); key.extend_from_slice(appservice_id.as_bytes());
key.push(0xff); key.push(0xff);
key.extend_from_slice(pdu_id); key.extend_from_slice(pdu_id);
self.servernamepduids.insert(key, b"")?; self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
Ok(()) Ok(())
} }
#[tracing::instrument] #[tracing::instrument]
fn calculate_hash(keys: &[Vec<u8>]) -> Vec<u8> { fn calculate_hash(keys: &[&[u8]]) -> Vec<u8> {
// We only hash the pdu's event ids, not the whole pdu // We only hash the pdu's event ids, not the whole pdu
let bytes = keys.join(&0xff); let bytes = keys.join(&0xff);
let hash = digest::digest(&digest::SHA256, &bytes); let hash = digest::digest(&digest::SHA256, &bytes);
@@ -346,33 +400,37 @@ impl Sending {
} }
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
async fn handle_event( async fn handle_events(
kind: OutgoingKind, kind: OutgoingKind,
pdu_ids: Vec<Vec<u8>>, events: Vec<SendingEventType>,
db: &Database, db: &Database,
) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> { ) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> {
match &kind { match &kind {
OutgoingKind::Appservice(server) => { OutgoingKind::Appservice(server) => {
let pdu_jsons = pdu_ids let mut pdu_jsons = Vec::new();
.iter()
.map(|pdu_id| { for event in &events {
Ok::<_, (Box<ServerName>, Error)>( match event {
db.rooms SendingEventType::Pdu(pdu_id) => {
.get_pdu_from_id(pdu_id) pdu_jsons.push(db.rooms
.map_err(|e| (server.clone(), e))? .get_pdu_from_id(&pdu_id)
.map_err(|e| (kind.clone(), e))?
.ok_or_else(|| { .ok_or_else(|| {
( (
server.clone(), kind.clone(),
Error::bad_database( Error::bad_database(
"[Appservice] Event in servernamepduids not found in db.", "[Appservice] Event in servernamepduids not found in db.",
), ),
) )
})? })?
.to_any_event(), .to_room_event())
) }
}) SendingEventType::Edu(_) => {
.filter_map(|r| r.ok()) // Appservices don't need EDUs (?)
.collect::<Vec<_>>(); }
}
}
let permit = db.sending.maximum_requests.acquire().await; let permit = db.sending.maximum_requests.acquire().await;
let response = appservice_server::send_request( let response = appservice_server::send_request(
@@ -384,7 +442,14 @@ impl Sending {
appservice::event::push_events::v1::Request { appservice::event::push_events::v1::Request {
events: &pdu_jsons, events: &pdu_jsons,
txn_id: &base64::encode_config( txn_id: &base64::encode_config(
Self::calculate_hash(&pdu_ids), Self::calculate_hash(
&events
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
})
.collect::<Vec<_>>(),
),
base64::URL_SAFE_NO_PAD, base64::URL_SAFE_NO_PAD,
), ),
}, },
@@ -398,25 +463,30 @@ impl Sending {
response response
} }
OutgoingKind::Push(user, pushkey) => { OutgoingKind::Push(user, pushkey) => {
let pdus = pdu_ids let mut pdus = Vec::new();
.iter()
.map(|pdu_id| { for event in &events {
Ok::<_, (Vec<u8>, Error)>( match event {
db.rooms SendingEventType::Pdu(pdu_id) => {
.get_pdu_from_id(pdu_id) pdus.push(
.map_err(|e| (pushkey.clone(), e))? db.rooms
.ok_or_else(|| { .get_pdu_from_id(&pdu_id)
( .map_err(|e| (kind.clone(), e))?
pushkey.clone(), .ok_or_else(|| {
Error::bad_database( (
"[Push] Event in servernamepduids not found in db.", kind.clone(),
), Error::bad_database(
) "[Push] Event in servernamepduids not found in db.",
})?, ),
) )
}) })?,
.filter_map(|r| r.ok()) );
.collect::<Vec<_>>(); }
SendingEventType::Edu(_) => {
// Push gateways don't need EDUs (?)
}
}
}
for pdu in pdus { for pdu in pdus {
// Redacted events are not notification targets (we don't send push for them) // Redacted events are not notification targets (we don't send push for them)
@@ -427,13 +497,13 @@ impl Sending {
let userid = let userid =
UserId::try_from(utils::string_from_bytes(user).map_err(|_| { UserId::try_from(utils::string_from_bytes(user).map_err(|_| {
( (
OutgoingKind::Push(user.clone(), pushkey.clone()), kind.clone(),
Error::bad_database("Invalid push user string in db."), Error::bad_database("Invalid push user string in db."),
) )
})?) })?)
.map_err(|_| { .map_err(|_| {
( (
OutgoingKind::Push(user.clone(), pushkey.clone()), kind.clone(),
Error::bad_database("Invalid push user id in db."), Error::bad_database("Invalid push user id in db."),
) )
})?; })?;
@@ -484,15 +554,17 @@ impl Sending {
Ok(OutgoingKind::Push(user.clone(), pushkey.clone())) Ok(OutgoingKind::Push(user.clone(), pushkey.clone()))
} }
OutgoingKind::Normal(server) => { OutgoingKind::Normal(server) => {
let pdu_jsons = pdu_ids let mut edu_jsons = Vec::new();
.iter() let mut pdu_jsons = Vec::new();
.map(|pdu_id| {
Ok::<_, (OutgoingKind, Error)>( for event in &events {
match event {
SendingEventType::Pdu(pdu_id) => {
// TODO: check room version and remove event_id if needed // TODO: check room version and remove event_id if needed
serde_json::from_str( pdu_jsons.push(serde_json::from_str(
PduEvent::convert_to_outgoing_federation_event( PduEvent::convert_to_outgoing_federation_event(
db.rooms db.rooms
.get_pdu_json_from_id(pdu_id) .get_pdu_json_from_id(&pdu_id)
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))? .map_err(|e| (OutgoingKind::Normal(server.clone()), e))?
.ok_or_else(|| { .ok_or_else(|| {
( (
@@ -506,11 +578,15 @@ impl Sending {
.json() .json()
.get(), .get(),
) )
.expect("Raw<..> is always valid"), .expect("Raw<..> is always valid"));
) }
}) SendingEventType::Edu(edu) => {
.filter_map(|r| r.ok()) edu_jsons.push(
.collect::<Vec<_>>(); serde_json::from_slice(edu).expect("Raw<..> is always valid"),
);
}
}
}
let permit = db.sending.maximum_requests.acquire().await; let permit = db.sending.maximum_requests.acquire().await;
@@ -520,10 +596,17 @@ impl Sending {
send_transaction_message::v1::Request { send_transaction_message::v1::Request {
origin: db.globals.server_name(), origin: db.globals.server_name(),
pdus: &pdu_jsons, pdus: &pdu_jsons,
edus: &[], edus: &edu_jsons,
origin_server_ts: SystemTime::now(), origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
transaction_id: &base64::encode_config( transaction_id: &base64::encode_config(
Self::calculate_hash(&pdu_ids), Self::calculate_hash(
&events
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
})
.collect::<Vec<_>>(),
),
base64::URL_SAFE_NO_PAD, base64::URL_SAFE_NO_PAD,
), ),
}, },
@@ -546,13 +629,13 @@ impl Sending {
} }
} }
fn parse_servercurrentpdus(key: &IVec) -> Result<(OutgoingKind, IVec)> { fn parse_servercurrentevent(key: &[u8]) -> Result<(OutgoingKind, SendingEventType)> {
// Appservices start with a plus // Appservices start with a plus
Ok::<_, Error>(if key.starts_with(b"+") { Ok::<_, Error>(if key.starts_with(b"+") {
let mut parts = key[1..].splitn(2, |&b| b == 0xff); let mut parts = key[1..].splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element"); let server = parts.next().expect("splitn always returns one element");
let pdu = parts let event = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?; .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| { let server = utils::string_from_bytes(&server).map_err(|_| {
@@ -563,7 +646,11 @@ impl Sending {
OutgoingKind::Appservice(Box::<ServerName>::try_from(server).map_err(|_| { OutgoingKind::Appservice(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction") Error::bad_database("Invalid server string in server_currenttransaction")
})?), })?),
IVec::from(pdu), if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
) )
} else if key.starts_with(b"$") { } else if key.starts_with(b"$") {
let mut parts = key[1..].splitn(3, |&b| b == 0xff); let mut parts = key[1..].splitn(3, |&b| b == 0xff);
@@ -572,18 +659,22 @@ impl Sending {
let pushkey = parts let pushkey = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?; .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let pdu = parts let event = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?; .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
( (
OutgoingKind::Push(user.to_vec(), pushkey.to_vec()), OutgoingKind::Push(user.to_vec(), pushkey.to_vec()),
IVec::from(pdu), if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
) )
} else { } else {
let mut parts = key.splitn(2, |&b| b == 0xff); let mut parts = key.splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element"); let server = parts.next().expect("splitn always returns one element");
let pdu = parts let event = parts
.next() .next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?; .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| { let server = utils::string_from_bytes(&server).map_err(|_| {
@@ -594,7 +685,11 @@ impl Sending {
OutgoingKind::Normal(Box::<ServerName>::try_from(server).map_err(|_| { OutgoingKind::Normal(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction") Error::bad_database("Invalid server string in server_currenttransaction")
})?), })?),
IVec::from(pdu), if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
) )
}) })
} }
+8 -6
View File
@@ -1,10 +1,12 @@
use std::sync::Arc;
use crate::Result; use crate::Result;
use ruma::{DeviceId, UserId}; use ruma::{DeviceId, UserId};
use sled::IVec;
#[derive(Clone)] use super::abstraction::Tree;
pub struct TransactionIds { pub struct TransactionIds {
pub(super) userdevicetxnid_response: sled::Tree, // Response can be empty (/sendToDevice) or the event id (/send) pub(super) userdevicetxnid_response: Arc<dyn Tree>, // Response can be empty (/sendToDevice) or the event id (/send)
} }
impl TransactionIds { impl TransactionIds {
@@ -21,7 +23,7 @@ impl TransactionIds {
key.push(0xff); key.push(0xff);
key.extend_from_slice(txn_id.as_bytes()); key.extend_from_slice(txn_id.as_bytes());
self.userdevicetxnid_response.insert(key, data)?; self.userdevicetxnid_response.insert(&key, data)?;
Ok(()) Ok(())
} }
@@ -31,7 +33,7 @@ impl TransactionIds {
user_id: &UserId, user_id: &UserId,
device_id: Option<&DeviceId>, device_id: Option<&DeviceId>,
txn_id: &str, txn_id: &str,
) -> Result<Option<IVec>> { ) -> Result<Option<Vec<u8>>> {
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(device_id.map(|d| d.as_bytes()).unwrap_or_default()); key.extend_from_slice(device_id.map(|d| d.as_bytes()).unwrap_or_default());
@@ -39,6 +41,6 @@ impl TransactionIds {
key.extend_from_slice(txn_id.as_bytes()); key.extend_from_slice(txn_id.as_bytes());
// If there's no entry, this is a new transaction // If there's no entry, this is a new transaction
Ok(self.userdevicetxnid_response.get(key)?) self.userdevicetxnid_response.get(&key)
} }
} }
+103 -33
View File
@@ -1,15 +1,20 @@
use crate::{Error, Result}; use std::sync::Arc;
use crate::{client_server::SESSION_ID_LENGTH, utils, Error, Result};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
r0::uiaa::{IncomingAuthData, UiaaInfo}, r0::uiaa::{IncomingAuthData, UiaaInfo},
}, },
signatures::CanonicalJsonValue,
DeviceId, UserId, DeviceId, UserId,
}; };
#[derive(Clone)] use super::abstraction::Tree;
pub struct Uiaa { pub struct Uiaa {
pub(super) userdeviceid_uiaainfo: sled::Tree, // User-interactive authentication pub(super) userdevicesessionid_uiaainfo: Arc<dyn Tree>, // User-interactive authentication
pub(super) userdevicesessionid_uiaarequest: Arc<dyn Tree>, // UiaaRequest = canonical json value
} }
impl Uiaa { impl Uiaa {
@@ -19,8 +24,20 @@ impl Uiaa {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
uiaainfo: &UiaaInfo, uiaainfo: &UiaaInfo,
json_body: &CanonicalJsonValue,
) -> Result<()> { ) -> Result<()> {
self.update_uiaa_session(user_id, device_id, Some(uiaainfo)) self.set_uiaa_request(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session should be set"), // TODO: better session error handling (why is it optional in ruma?)
json_body,
)?;
self.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session should be set"),
Some(uiaainfo),
)
} }
pub fn try_auth( pub fn try_auth(
@@ -40,11 +57,13 @@ impl Uiaa {
{ {
let mut uiaainfo = session let mut uiaainfo = session
.as_ref() .as_ref()
.map(|session| { .map(|session| self.get_uiaa_session(&user_id, &device_id, session))
Ok::<_, Error>(self.get_uiaa_session(&user_id, &device_id, session)?)
})
.unwrap_or_else(|| Ok(uiaainfo.clone()))?; .unwrap_or_else(|| Ok(uiaainfo.clone()))?;
if uiaainfo.session.is_none() {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
}
// Find out what the user completed // Find out what the user completed
match &**kind { match &**kind {
"m.login.password" => { "m.login.password" => {
@@ -130,35 +149,96 @@ impl Uiaa {
} }
if !completed { if !completed {
self.update_uiaa_session(user_id, device_id, Some(&uiaainfo))?; self.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
Some(&uiaainfo),
)?;
return Ok((false, uiaainfo)); return Ok((false, uiaainfo));
} }
// UIAA was successful! Remove this session and return true // UIAA was successful! Remove this session and return true
self.update_uiaa_session(user_id, device_id, None)?; self.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
None,
)?;
Ok((true, uiaainfo)) Ok((true, uiaainfo))
} else { } else {
panic!("FallbackAcknowledgement is not supported yet"); panic!("FallbackAcknowledgement is not supported yet");
} }
} }
fn set_uiaa_request(
&self,
user_id: &UserId,
device_id: &DeviceId,
session: &str,
request: &CanonicalJsonValue,
) -> Result<()> {
let mut userdevicesessionid = user_id.as_bytes().to_vec();
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(device_id.as_bytes());
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(session.as_bytes());
self.userdevicesessionid_uiaarequest.insert(
&userdevicesessionid,
&serde_json::to_vec(request).expect("json value to vec always works"),
)?;
Ok(())
}
pub fn get_uiaa_request(
&self,
user_id: &UserId,
device_id: &DeviceId,
session: &str,
) -> Result<Option<CanonicalJsonValue>> {
let mut userdevicesessionid = user_id.as_bytes().to_vec();
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(device_id.as_bytes());
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(session.as_bytes());
self.userdevicesessionid_uiaarequest
.get(&userdevicesessionid)?
.map_or(Ok(None), |bytes| {
Ok::<_, Error>(Some(
serde_json::from_str::<CanonicalJsonValue>(
&utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Invalid uiaa request bytes in db.")
})?,
)
.map_err(|_| Error::bad_database("Invalid uiaa request in db."))?,
))
})
}
fn update_uiaa_session( fn update_uiaa_session(
&self, &self,
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
session: &str,
uiaainfo: Option<&UiaaInfo>, uiaainfo: Option<&UiaaInfo>,
) -> Result<()> { ) -> Result<()> {
let mut userdeviceid = user_id.as_bytes().to_vec(); let mut userdevicesessionid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdevicesessionid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdevicesessionid.extend_from_slice(device_id.as_bytes());
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(session.as_bytes());
if let Some(uiaainfo) = uiaainfo { if let Some(uiaainfo) = uiaainfo {
self.userdeviceid_uiaainfo.insert( self.userdevicesessionid_uiaainfo.insert(
&userdeviceid, &userdevicesessionid,
&*serde_json::to_string(&uiaainfo).expect("UiaaInfo::to_string always works"), &serde_json::to_vec(&uiaainfo).expect("UiaaInfo::to_vec always works"),
)?; )?;
} else { } else {
self.userdeviceid_uiaainfo.remove(&userdeviceid)?; self.userdevicesessionid_uiaainfo
.remove(&userdevicesessionid)?;
} }
Ok(()) Ok(())
@@ -170,14 +250,16 @@ impl Uiaa {
device_id: &DeviceId, device_id: &DeviceId,
session: &str, session: &str,
) -> Result<UiaaInfo> { ) -> Result<UiaaInfo> {
let mut userdeviceid = user_id.as_bytes().to_vec(); let mut userdevicesessionid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdevicesessionid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdevicesessionid.extend_from_slice(device_id.as_bytes());
userdevicesessionid.push(0xff);
userdevicesessionid.extend_from_slice(session.as_bytes());
let uiaainfo = serde_json::from_slice::<UiaaInfo>( let uiaainfo = serde_json::from_slice::<UiaaInfo>(
&self &self
.userdeviceid_uiaainfo .userdevicesessionid_uiaainfo
.get(&userdeviceid)? .get(&userdevicesessionid)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
"UIAA session does not exist.", "UIAA session does not exist.",
@@ -185,18 +267,6 @@ impl Uiaa {
) )
.map_err(|_| Error::bad_database("UiaaInfo in userdeviceid_uiaainfo is invalid."))?; .map_err(|_| Error::bad_database("UiaaInfo in userdeviceid_uiaainfo is invalid."))?;
if uiaainfo
.session
.as_ref()
.filter(|&s| s == session)
.is_none()
{
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"UIAA session token invalid.",
));
}
Ok(uiaainfo) Ok(uiaainfo)
} }
} }
+146 -164
View File
@@ -1,52 +1,47 @@
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use ruma::{ use ruma::{
api::client::{ api::client::{error::ErrorKind, r0::device::Device},
error::ErrorKind, encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
r0::{
device::Device,
keys::{CrossSigningKey, OneTimeKey},
},
},
encryption::DeviceKeys,
events::{AnyToDeviceEvent, EventType}, events::{AnyToDeviceEvent, EventType},
identifiers::MxcUri, identifiers::MxcUri,
serde::Raw, serde::Raw,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UInt, UserId, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, UInt, UserId,
}; };
use std::{collections::BTreeMap, convert::TryFrom, mem, time::SystemTime}; use std::{collections::BTreeMap, convert::TryFrom, mem, sync::Arc};
use super::abstraction::Tree;
#[derive(Clone)]
pub struct Users { pub struct Users {
pub(super) userid_password: sled::Tree, pub(super) userid_password: Arc<dyn Tree>,
pub(super) userid_displayname: sled::Tree, pub(super) userid_displayname: Arc<dyn Tree>,
pub(super) userid_avatarurl: sled::Tree, pub(super) userid_avatarurl: Arc<dyn Tree>,
pub(super) userdeviceid_token: sled::Tree, pub(super) userdeviceid_token: Arc<dyn Tree>,
pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists pub(super) userdeviceid_metadata: Arc<dyn Tree>, // This is also used to check if a device exists
pub(super) userid_devicelistversion: sled::Tree, // DevicelistVersion = u64 pub(super) userid_devicelistversion: Arc<dyn Tree>, // DevicelistVersion = u64
pub(super) token_userdeviceid: sled::Tree, pub(super) token_userdeviceid: Arc<dyn Tree>,
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + DeviceKeyId pub(super) onetimekeyid_onetimekeys: Arc<dyn Tree>, // OneTimeKeyId = UserId + DeviceKeyId
pub(super) userid_lastonetimekeyupdate: sled::Tree, // LastOneTimeKeyUpdate = Count pub(super) userid_lastonetimekeyupdate: Arc<dyn Tree>, // LastOneTimeKeyUpdate = Count
pub(super) keychangeid_userid: sled::Tree, // KeyChangeId = UserId/RoomId + Count pub(super) keychangeid_userid: Arc<dyn Tree>, // KeyChangeId = UserId/RoomId + Count
pub(super) keyid_key: sled::Tree, // KeyId = UserId + KeyId (depends on key type) pub(super) keyid_key: Arc<dyn Tree>, // KeyId = UserId + KeyId (depends on key type)
pub(super) userid_masterkeyid: sled::Tree, pub(super) userid_masterkeyid: Arc<dyn Tree>,
pub(super) userid_selfsigningkeyid: sled::Tree, pub(super) userid_selfsigningkeyid: Arc<dyn Tree>,
pub(super) userid_usersigningkeyid: sled::Tree, pub(super) userid_usersigningkeyid: Arc<dyn Tree>,
pub(super) todeviceid_events: sled::Tree, // ToDeviceId = UserId + DeviceId + Count pub(super) todeviceid_events: Arc<dyn Tree>, // ToDeviceId = UserId + DeviceId + Count
} }
impl Users { impl Users {
/// Check if a user has an account on this homeserver. /// Check if a user has an account on this homeserver.
pub fn exists(&self, user_id: &UserId) -> Result<bool> { pub fn exists(&self, user_id: &UserId) -> Result<bool> {
Ok(self.userid_password.contains_key(user_id.to_string())?) Ok(self.userid_password.get(user_id.as_bytes())?.is_some())
} }
/// Check if account is deactivated /// Check if account is deactivated
pub fn is_deactivated(&self, user_id: &UserId) -> Result<bool> { pub fn is_deactivated(&self, user_id: &UserId) -> Result<bool> {
Ok(self Ok(self
.userid_password .userid_password
.get(user_id.to_string())? .get(user_id.as_bytes())?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"User does not exist.", "User does not exist.",
@@ -55,20 +50,20 @@ impl Users {
} }
/// Create a new user account on this homeserver. /// Create a new user account on this homeserver.
pub fn create(&self, user_id: &UserId, password: &str) -> Result<()> { pub fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
self.set_password(user_id, password)?; self.set_password(user_id, password)?;
Ok(()) Ok(())
} }
/// Returns the number of users registered on this server. /// Returns the number of users registered on this server.
pub fn count(&self) -> usize { pub fn count(&self) -> Result<usize> {
self.userid_password.iter().count() Ok(self.userid_password.iter().count())
} }
/// Find out which user an access token belongs to. /// Find out which user an access token belongs to.
pub fn find_from_token(&self, token: &str) -> Result<Option<(UserId, String)>> { pub fn find_from_token(&self, token: &str) -> Result<Option<(UserId, String)>> {
self.token_userdeviceid self.token_userdeviceid
.get(token)? .get(token.as_bytes())?
.map_or(Ok(None), |bytes| { .map_or(Ok(None), |bytes| {
let mut parts = bytes.split(|&b| b == 0xff); let mut parts = bytes.split(|&b| b == 0xff);
let user_bytes = parts.next().ok_or_else(|| { let user_bytes = parts.next().ok_or_else(|| {
@@ -93,21 +88,19 @@ impl Users {
} }
/// Returns an iterator over all users on this homeserver. /// Returns an iterator over all users on this homeserver.
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> { pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> + '_ {
self.userid_password.iter().keys().map(|bytes| { self.userid_password.iter().map(|(bytes, _)| {
Ok( UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
UserId::try_from(utils::string_from_bytes(&bytes?).map_err(|_| { Error::bad_database("User ID in userid_password is invalid unicode.")
Error::bad_database("User ID in userid_password is invalid unicode.") })?)
})?) .map_err(|_| Error::bad_database("User ID in userid_password is invalid."))
.map_err(|_| Error::bad_database("User ID in userid_password is invalid."))?,
)
}) })
} }
/// Returns the password hash for the given user. /// Returns the password hash for the given user.
pub fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> { pub fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> {
self.userid_password self.userid_password
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map_or(Ok(None), |bytes| { .map_or(Ok(None), |bytes| {
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Password hash in db is not valid string.") Error::bad_database("Password hash in db is not valid string.")
@@ -116,22 +109,28 @@ impl Users {
} }
/// Hash and set the user's password to the Argon2 hash /// Hash and set the user's password to the Argon2 hash
pub fn set_password(&self, user_id: &UserId, password: &str) -> Result<()> { pub fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
if let Ok(hash) = utils::calculate_hash(&password) { if let Some(password) = password {
self.userid_password.insert(user_id.to_string(), &*hash)?; if let Ok(hash) = utils::calculate_hash(&password) {
Ok(()) self.userid_password
.insert(user_id.as_bytes(), hash.as_bytes())?;
Ok(())
} else {
Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Password does not meet the requirements.",
))
}
} else { } else {
Err(Error::BadRequest( self.userid_password.insert(user_id.as_bytes(), b"")?;
ErrorKind::InvalidParam, Ok(())
"Password does not meet the requirements.",
))
} }
} }
/// Returns the displayname of a user on this homeserver. /// Returns the displayname of a user on this homeserver.
pub fn displayname(&self, user_id: &UserId) -> Result<Option<String>> { pub fn displayname(&self, user_id: &UserId) -> Result<Option<String>> {
self.userid_displayname self.userid_displayname
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map_or(Ok(None), |bytes| { .map_or(Ok(None), |bytes| {
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Displayname in db is invalid.") Error::bad_database("Displayname in db is invalid.")
@@ -143,9 +142,9 @@ impl Users {
pub fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()> { pub fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()> {
if let Some(displayname) = displayname { if let Some(displayname) = displayname {
self.userid_displayname self.userid_displayname
.insert(user_id.to_string(), &*displayname)?; .insert(user_id.as_bytes(), displayname.as_bytes())?;
} else { } else {
self.userid_displayname.remove(user_id.to_string())?; self.userid_displayname.remove(user_id.as_bytes())?;
} }
Ok(()) Ok(())
@@ -154,7 +153,7 @@ impl Users {
/// Get a the avatar_url of a user. /// Get a the avatar_url of a user.
pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<MxcUri>> { pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<MxcUri>> {
self.userid_avatarurl self.userid_avatarurl
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map(|bytes| { .map(|bytes| {
let s = utils::string_from_bytes(&bytes) let s = utils::string_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?; .map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
@@ -167,9 +166,9 @@ impl Users {
pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<MxcUri>) -> Result<()> { pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<MxcUri>) -> Result<()> {
if let Some(avatar_url) = avatar_url { if let Some(avatar_url) = avatar_url {
self.userid_avatarurl self.userid_avatarurl
.insert(user_id.to_string(), avatar_url.to_string().as_str())?; .insert(user_id.as_bytes(), avatar_url.to_string().as_bytes())?;
} else { } else {
self.userid_avatarurl.remove(user_id.to_string())?; self.userid_avatarurl.remove(user_id.as_bytes())?;
} }
Ok(()) Ok(())
@@ -191,19 +190,17 @@ impl Users {
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
self.userid_devicelistversion self.userid_devicelistversion
.update_and_fetch(&user_id.as_bytes(), utils::increment)? .increment(user_id.as_bytes())?;
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.insert( self.userdeviceid_metadata.insert(
userdeviceid, &userdeviceid,
serde_json::to_string(&Device { &serde_json::to_vec(&Device {
device_id: device_id.into(), device_id: device_id.into(),
display_name: initial_device_display_name, display_name: initial_device_display_name,
last_seen_ip: None, // TODO last_seen_ip: None, // TODO
last_seen_ts: Some(SystemTime::now()), last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()),
}) })
.expect("Device::to_string never fails.") .expect("Device::to_string never fails."),
.as_bytes(),
)?; )?;
self.set_token(user_id, &device_id, token)?; self.set_token(user_id, &device_id, token)?;
@@ -218,7 +215,8 @@ impl Users {
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
// Remove tokens // Remove tokens
if let Some(old_token) = self.userdeviceid_token.remove(&userdeviceid)? { if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? {
self.userdeviceid_token.remove(&userdeviceid)?;
self.token_userdeviceid.remove(&old_token)?; self.token_userdeviceid.remove(&old_token)?;
} }
@@ -226,15 +224,14 @@ impl Users {
let mut prefix = userdeviceid.clone(); let mut prefix = userdeviceid.clone();
prefix.push(0xff); prefix.push(0xff);
for key in self.todeviceid_events.scan_prefix(&prefix).keys() { for (key, _) in self.todeviceid_events.scan_prefix(prefix) {
self.todeviceid_events.remove(key?)?; self.todeviceid_events.remove(&key)?;
} }
// TODO: Remove onetimekeys // TODO: Remove onetimekeys
self.userid_devicelistversion self.userid_devicelistversion
.update_and_fetch(&user_id.as_bytes(), utils::increment)? .increment(user_id.as_bytes())?;
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.remove(&userdeviceid)?; self.userdeviceid_metadata.remove(&userdeviceid)?;
@@ -242,16 +239,18 @@ impl Users {
} }
/// Returns an iterator over all device ids of this user. /// Returns an iterator over all device ids of this user.
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<Box<DeviceId>>> { pub fn all_device_ids<'a>(
&'a self,
user_id: &UserId,
) -> impl Iterator<Item = Result<Box<DeviceId>>> + 'a {
let mut prefix = user_id.as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
// All devices have metadata // All devices have metadata
self.userdeviceid_metadata self.userdeviceid_metadata
.scan_prefix(prefix) .scan_prefix(prefix)
.keys() .map(|(bytes, _)| {
.map(|bytes| {
Ok(utils::string_from_bytes( Ok(utils::string_from_bytes(
&*bytes? &bytes
.rsplit(|&b| b == 0xff) .rsplit(|&b| b == 0xff)
.next() .next()
.ok_or_else(|| Error::bad_database("UserDevice ID in db is invalid."))?, .ok_or_else(|| Error::bad_database("UserDevice ID in db is invalid."))?,
@@ -272,13 +271,15 @@ impl Users {
// Remove old token // Remove old token
if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? { if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? {
self.token_userdeviceid.remove(old_token)?; self.token_userdeviceid.remove(&old_token)?;
// It will be removed from userdeviceid_token by the insert later // It will be removed from userdeviceid_token by the insert later
} }
// Assign token to user device combination // Assign token to user device combination
self.userdeviceid_token.insert(&userdeviceid, &*token)?; self.userdeviceid_token
self.token_userdeviceid.insert(token, userdeviceid)?; .insert(&userdeviceid, token.as_bytes())?;
self.token_userdeviceid
.insert(token.as_bytes(), &userdeviceid)?;
Ok(()) Ok(())
} }
@@ -310,8 +311,7 @@ impl Users {
self.onetimekeyid_onetimekeys.insert( self.onetimekeyid_onetimekeys.insert(
&key, &key,
&*serde_json::to_string(&one_time_key_value) &serde_json::to_vec(&one_time_key_value).expect("OneTimeKey::to_vec always works"),
.expect("OneTimeKey::to_string always works"),
)?; )?;
self.userid_lastonetimekeyupdate self.userid_lastonetimekeyupdate
@@ -351,10 +351,9 @@ impl Users {
.insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?; .insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
self.onetimekeyid_onetimekeys self.onetimekeyid_onetimekeys
.scan_prefix(&prefix) .scan_prefix(prefix)
.next() .next()
.map(|r| { .map(|(key, value)| {
let (key, value) = r?;
self.onetimekeyid_onetimekeys.remove(&key)?; self.onetimekeyid_onetimekeys.remove(&key)?;
Ok(( Ok((
@@ -384,21 +383,20 @@ impl Users {
let mut counts = BTreeMap::new(); let mut counts = BTreeMap::new();
for algorithm in self for algorithm in
.onetimekeyid_onetimekeys self.onetimekeyid_onetimekeys
.scan_prefix(&userdeviceid) .scan_prefix(userdeviceid)
.keys() .map(|(bytes, _)| {
.map(|bytes| { Ok::<_, Error>(
Ok::<_, Error>( serde_json::from_slice::<DeviceKeyId>(
serde_json::from_slice::<DeviceKeyId>( &*bytes.rsplit(|&b| b == 0xff).next().ok_or_else(|| {
&*bytes?.rsplit(|&b| b == 0xff).next().ok_or_else(|| { Error::bad_database("OneTimeKey ID in db is invalid.")
Error::bad_database("OneTimeKey ID in db is invalid.") })?,
})?, )
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))?
.algorithm(),
) )
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))? })
.algorithm(),
)
})
{ {
*counts.entry(algorithm?).or_default() += UInt::from(1_u32); *counts.entry(algorithm?).or_default() += UInt::from(1_u32);
} }
@@ -420,7 +418,7 @@ impl Users {
self.keyid_key.insert( self.keyid_key.insert(
&userdeviceid, &userdeviceid,
&*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"), &serde_json::to_vec(&device_keys).expect("DeviceKeys::to_vec always works"),
)?; )?;
self.mark_device_key_update(user_id, rooms, globals)?; self.mark_device_key_update(user_id, rooms, globals)?;
@@ -461,11 +459,11 @@ impl Users {
self.keyid_key.insert( self.keyid_key.insert(
&master_key_key, &master_key_key,
&*serde_json::to_string(&master_key).expect("CrossSigningKey::to_string always works"), &serde_json::to_vec(&master_key).expect("CrossSigningKey::to_vec always works"),
)?; )?;
self.userid_masterkeyid self.userid_masterkeyid
.insert(&*user_id.to_string(), master_key_key)?; .insert(user_id.as_bytes(), &master_key_key)?;
// Self-signing key // Self-signing key
if let Some(self_signing_key) = self_signing_key { if let Some(self_signing_key) = self_signing_key {
@@ -487,12 +485,12 @@ impl Users {
self.keyid_key.insert( self.keyid_key.insert(
&self_signing_key_key, &self_signing_key_key,
&*serde_json::to_string(&self_signing_key) &serde_json::to_vec(&self_signing_key)
.expect("CrossSigningKey::to_string always works"), .expect("CrossSigningKey::to_vec always works"),
)?; )?;
self.userid_selfsigningkeyid self.userid_selfsigningkeyid
.insert(&*user_id.to_string(), self_signing_key_key)?; .insert(user_id.as_bytes(), &self_signing_key_key)?;
} }
// User-signing key // User-signing key
@@ -515,12 +513,12 @@ impl Users {
self.keyid_key.insert( self.keyid_key.insert(
&user_signing_key_key, &user_signing_key_key,
&*serde_json::to_string(&user_signing_key) &serde_json::to_vec(&user_signing_key)
.expect("CrossSigningKey::to_string always works"), .expect("CrossSigningKey::to_vec always works"),
)?; )?;
self.userid_usersigningkeyid self.userid_usersigningkeyid
.insert(&*user_id.to_string(), user_signing_key_key)?; .insert(user_id.as_bytes(), &user_signing_key_key)?;
} }
self.mark_device_key_update(user_id, rooms, globals)?; self.mark_device_key_update(user_id, rooms, globals)?;
@@ -562,8 +560,7 @@ impl Users {
self.keyid_key.insert( self.keyid_key.insert(
&key, &key,
&*serde_json::to_string(&cross_signing_key) &serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
.expect("CrossSigningKey::to_string always works"),
)?; )?;
// TODO: Should we notify about this change? // TODO: Should we notify about this change?
@@ -573,36 +570,26 @@ impl Users {
} }
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn keys_changed( pub fn keys_changed<'a>(
&self, &'a self,
user_or_room_id: &str, user_or_room_id: &str,
from: u64, from: u64,
to: Option<u64>, to: Option<u64>,
) -> impl Iterator<Item = Result<UserId>> { ) -> impl Iterator<Item = Result<UserId>> + 'a {
let mut prefix = user_or_room_id.as_bytes().to_vec(); let mut prefix = user_or_room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut start = prefix.clone(); let mut start = prefix.clone();
start.extend_from_slice(&(from + 1).to_be_bytes()); start.extend_from_slice(&(from + 1).to_be_bytes());
let mut end = prefix.clone();
end.extend_from_slice(&to.unwrap_or(u64::MAX).to_be_bytes());
self.keychangeid_userid self.keychangeid_userid
.range(start..end) .iter_from(&start, false)
.filter_map(|r| r.ok())
.take_while(move |(k, _)| k.starts_with(&prefix)) .take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(_, bytes)| { .map(|(_, bytes)| {
Ok( UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| { Error::bad_database("User ID in devicekeychangeid_userid is invalid unicode.")
Error::bad_database( })?)
"User ID in devicekeychangeid_userid is invalid unicode.", .map_err(|_| Error::bad_database("User ID in devicekeychangeid_userid is invalid."))
)
})?)
.map_err(|_| {
Error::bad_database("User ID in devicekeychangeid_userid is invalid.")
})?,
)
}) })
} }
@@ -626,13 +613,13 @@ impl Users {
key.push(0xff); key.push(0xff);
key.extend_from_slice(&count); key.extend_from_slice(&count);
self.keychangeid_userid.insert(key, &*user_id.to_string())?; self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
} }
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&count); key.extend_from_slice(&count);
self.keychangeid_userid.insert(key, &*user_id.to_string())?; self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
Ok(()) Ok(())
} }
@@ -646,23 +633,22 @@ impl Users {
key.push(0xff); key.push(0xff);
key.extend_from_slice(device_id.as_bytes()); key.extend_from_slice(device_id.as_bytes());
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| { self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| { Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
Error::bad_database("DeviceKeys in db are invalid.") Error::bad_database("DeviceKeys in db are invalid.")
})?)) })?))
}) })
} }
pub fn get_master_key( pub fn get_master_key<F: Fn(&UserId) -> bool>(
&self, &self,
user_id: &UserId, user_id: &UserId,
sender_id: &UserId, allowed_signatures: F,
) -> Result<Option<CrossSigningKey>> { ) -> Result<Option<CrossSigningKey>> {
// TODO: hide some signatures
self.userid_masterkeyid self.userid_masterkeyid
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map_or(Ok(None), |key| { .map_or(Ok(None), |key| {
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| { self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes) let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
.map_err(|_| { .map_err(|_| {
Error::bad_database("CrossSigningKey in db is invalid.") Error::bad_database("CrossSigningKey in db is invalid.")
@@ -673,7 +659,7 @@ impl Users {
cross_signing_key.signatures = cross_signing_key cross_signing_key.signatures = cross_signing_key
.signatures .signatures
.into_iter() .into_iter()
.filter(|(user, _)| user == user_id || user == sender_id) .filter(|(user, _)| allowed_signatures(user))
.collect(); .collect();
Ok(Some(cross_signing_key)) Ok(Some(cross_signing_key))
@@ -681,15 +667,15 @@ impl Users {
}) })
} }
pub fn get_self_signing_key( pub fn get_self_signing_key<F: Fn(&UserId) -> bool>(
&self, &self,
user_id: &UserId, user_id: &UserId,
sender_id: &UserId, allowed_signatures: F,
) -> Result<Option<CrossSigningKey>> { ) -> Result<Option<CrossSigningKey>> {
self.userid_selfsigningkeyid self.userid_selfsigningkeyid
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map_or(Ok(None), |key| { .map_or(Ok(None), |key| {
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| { self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes) let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
.map_err(|_| { .map_err(|_| {
Error::bad_database("CrossSigningKey in db is invalid.") Error::bad_database("CrossSigningKey in db is invalid.")
@@ -700,7 +686,7 @@ impl Users {
cross_signing_key.signatures = cross_signing_key cross_signing_key.signatures = cross_signing_key
.signatures .signatures
.into_iter() .into_iter()
.filter(|(user, _)| user == user_id || user == sender_id) .filter(|(user, _)| user == user_id || allowed_signatures(user))
.collect(); .collect();
Ok(Some(cross_signing_key)) Ok(Some(cross_signing_key))
@@ -710,9 +696,9 @@ impl Users {
pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<CrossSigningKey>> { pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<CrossSigningKey>> {
self.userid_usersigningkeyid self.userid_usersigningkeyid
.get(user_id.to_string())? .get(user_id.as_bytes())?
.map_or(Ok(None), |key| { .map_or(Ok(None), |key| {
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| { self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| { Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
Error::bad_database("CrossSigningKey in db is invalid.") Error::bad_database("CrossSigningKey in db is invalid.")
})?)) })?))
@@ -725,7 +711,7 @@ impl Users {
sender: &UserId, sender: &UserId,
target_user_id: &UserId, target_user_id: &UserId,
target_device_id: &DeviceId, target_device_id: &DeviceId,
event_type: &EventType, event_type: &str,
content: serde_json::Value, content: serde_json::Value,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
@@ -736,14 +722,13 @@ impl Users {
key.extend_from_slice(&globals.next_count()?.to_be_bytes()); key.extend_from_slice(&globals.next_count()?.to_be_bytes());
let mut json = serde_json::Map::new(); let mut json = serde_json::Map::new();
json.insert("type".to_owned(), event_type.to_string().into()); json.insert("type".to_owned(), event_type.to_owned().into());
json.insert("sender".to_owned(), sender.to_string().into()); json.insert("sender".to_owned(), sender.to_string().into());
json.insert("content".to_owned(), content); json.insert("content".to_owned(), content);
self.todeviceid_events.insert( let value = serde_json::to_vec(&json).expect("Map::to_vec always works");
&key,
&*serde_json::to_string(&json).expect("Map::to_string always works"), self.todeviceid_events.insert(&key, &value)?;
)?;
Ok(()) Ok(())
} }
@@ -761,9 +746,9 @@ impl Users {
prefix.extend_from_slice(device_id.as_bytes()); prefix.extend_from_slice(device_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
for value in self.todeviceid_events.scan_prefix(&prefix).values() { for (_, value) in self.todeviceid_events.scan_prefix(prefix) {
events.push( events.push(
serde_json::from_slice(&*value?) serde_json::from_slice(&value)
.map_err(|_| Error::bad_database("Event in todeviceid_events is invalid."))?, .map_err(|_| Error::bad_database("Event in todeviceid_events is invalid."))?,
); );
} }
@@ -788,10 +773,9 @@ impl Users {
for (key, _) in self for (key, _) in self
.todeviceid_events .todeviceid_events
.range(&*prefix..=&*last) .iter_from(&last, true) // this includes last
.keys() .take_while(move |(k, _)| k.starts_with(&prefix))
.map(|key| { .map(|(key, _)| {
let key = key?;
Ok::<_, Error>(( Ok::<_, Error>((
key.clone(), key.clone(),
utils::u64_from_bytes(&key[key.len() - mem::size_of::<u64>()..key.len()]) utils::u64_from_bytes(&key[key.len() - mem::size_of::<u64>()..key.len()])
@@ -801,7 +785,7 @@ impl Users {
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.take_while(|&(_, count)| count <= until) .take_while(|&(_, count)| count <= until)
{ {
self.todeviceid_events.remove(key)?; self.todeviceid_events.remove(&key)?;
} }
Ok(()) Ok(())
@@ -821,14 +805,11 @@ impl Users {
assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some()); assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some());
self.userid_devicelistversion self.userid_devicelistversion
.update_and_fetch(&user_id.as_bytes(), utils::increment)? .increment(user_id.as_bytes())?;
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.insert( self.userdeviceid_metadata.insert(
userdeviceid, &userdeviceid,
serde_json::to_string(device) &serde_json::to_vec(device).expect("Device::to_string always works"),
.expect("Device::to_string always works")
.as_bytes(),
)?; )?;
Ok(()) Ok(())
@@ -863,17 +844,18 @@ impl Users {
}) })
} }
pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> { pub fn all_devices_metadata<'a>(
&'a self,
user_id: &UserId,
) -> impl Iterator<Item = Result<Device>> + 'a {
let mut key = user_id.as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
self.userdeviceid_metadata self.userdeviceid_metadata
.scan_prefix(key) .scan_prefix(key)
.values() .map(|(_, bytes)| {
.map(|bytes| { serde_json::from_slice::<Device>(&bytes)
Ok(serde_json::from_slice::<Device>(&bytes?).map_err(|_| { .map_err(|_| Error::bad_database("Device in userdeviceid_metadata is invalid."))
Error::bad_database("Device in userdeviceid_metadata is invalid.")
})?)
}) })
} }
@@ -887,7 +869,7 @@ impl Users {
// Set the password to "" to indicate a deactivated account. Hashes will never result in an // Set the password to "" to indicate a deactivated account. Hashes will never result in an
// empty string, so the user will not be able to log in again. Systems like changing the // empty string, so the user will not be able to log in again. Systems like changing the
// password without logging in should check if the account is deactivated. // password without logging in should check if the account is deactivated.
self.userid_password.insert(user_id.to_string(), "")?; self.userid_password.insert(user_id.as_bytes(), &[])?;
// TODO: Unhook 3PID // TODO: Unhook 3PID
Ok(()) Ok(())
+48 -16
View File
@@ -1,5 +1,11 @@
use log::error; use log::{error, warn};
use ruma::api::client::{error::ErrorKind, r0::uiaa::UiaaInfo}; use ruma::{
api::client::{
error::{Error as RumaError, ErrorKind},
r0::uiaa::UiaaInfo,
},
ServerName,
};
use thiserror::Error; use thiserror::Error;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@@ -10,18 +16,25 @@ use {
response::{self, Responder}, response::{self, Responder},
Request, Request,
}, },
ruma::api::client::{error::Error as RumaError, r0::uiaa::UiaaResponse}, ruma::api::client::r0::uiaa::UiaaResponse,
}; };
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("There was a problem with the connection to the database.")] #[cfg(feature = "sled")]
#[error("There was a problem with the connection to the sled database.")]
SledError { SledError {
#[from] #[from]
source: sled::Error, source: sled::Error,
}, },
#[cfg(feature = "rocksdb")]
#[error("There was a problem with the connection to the rocksdb database: {source}")]
RocksDbError {
#[from]
source: rocksdb::Error,
},
#[error("Could not generate an image.")] #[error("Could not generate an image.")]
ImageError { ImageError {
#[from] #[from]
@@ -33,6 +46,13 @@ pub enum Error {
source: reqwest::Error, source: reqwest::Error,
}, },
#[error("{0}")] #[error("{0}")]
FederationError(Box<ServerName>, RumaError),
#[error("Could not do this io: {source}")]
IoError {
#[from]
source: std::io::Error,
},
#[error("{0}")]
BadServerResponse(&'static str), BadServerResponse(&'static str),
#[error("{0}")] #[error("{0}")]
BadConfig(&'static str), BadConfig(&'static str),
@@ -41,7 +61,6 @@ pub enum Error {
BadDatabase(&'static str), BadDatabase(&'static str),
#[error("uiaa")] #[error("uiaa")]
Uiaa(UiaaInfo), Uiaa(UiaaInfo),
#[error("{0}: {1}")] #[error("{0}: {1}")]
BadRequest(ErrorKind, &'static str), BadRequest(ErrorKind, &'static str),
#[error("{0}")] #[error("{0}")]
@@ -60,14 +79,16 @@ impl Error {
} }
} }
#[cfg(feature = "conduit_bin")] impl Error {
impl<'r, 'o> Responder<'r, 'o> for Error pub fn to_response(&self) -> RumaResponse<UiaaResponse> {
where if let Self::Uiaa(uiaainfo) = self {
'o: 'r, return RumaResponse(UiaaResponse::AuthResponse(uiaainfo.clone()));
{ }
fn respond_to(self, r: &'r Request<'_>) -> response::Result<'o> {
if let Self::Uiaa(uiaainfo) = &self { if let Self::FederationError(origin, error) = self {
return RumaResponse::from(UiaaResponse::AuthResponse(uiaainfo.clone())).respond_to(r); let mut error = error.clone();
error.message = format!("Answer from {}: {}", origin, error.message);
return RumaResponse(UiaaResponse::MatrixError(error));
} }
let message = format!("{}", self); let message = format!("{}", self);
@@ -92,11 +113,22 @@ where
_ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR), _ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR),
}; };
RumaResponse::from(RumaError { warn!("{}: {}", status_code, message);
RumaResponse(UiaaResponse::MatrixError(RumaError {
kind, kind,
message, message,
status_code, status_code,
}) }))
.respond_to(r) }
}
#[cfg(feature = "conduit_bin")]
impl<'r, 'o> Responder<'r, 'o> for Error
where
'o: 'r,
{
fn respond_to(self, r: &'r Request<'_>) -> response::Result<'o> {
self.to_response().respond_to(r)
} }
} }
+2
View File
@@ -1,4 +1,6 @@
#![allow(clippy::suspicious_else_formatting)] #![allow(clippy::suspicious_else_formatting)]
#![deny(clippy::dbg_macro)]
pub mod appservice_server; pub mod appservice_server;
pub mod client_server; pub mod client_server;
mod database; mod database;
+18 -9
View File
@@ -1,5 +1,6 @@
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
#![allow(clippy::suspicious_else_formatting)] #![allow(clippy::suspicious_else_formatting)]
#![deny(clippy::dbg_macro)]
pub mod appservice_server; pub mod appservice_server;
pub mod client_server; pub mod client_server;
@@ -11,6 +12,8 @@ mod pdu;
mod ruma_wrapper; mod ruma_wrapper;
mod utils; mod utils;
use std::sync::Arc;
use database::Config; use database::Config;
pub use database::Database; pub use database::Database;
pub use error::{Error, Result}; pub use error::{Error, Result};
@@ -30,7 +33,7 @@ use rocket::{
use tracing::span; use tracing::span;
use tracing_subscriber::{prelude::*, Registry}; use tracing_subscriber::{prelude::*, Registry};
fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build> { fn setup_rocket(config: Figment, data: Arc<Database>) -> rocket::Rocket<rocket::Build> {
rocket::custom(config) rocket::custom(config)
.manage(data) .manage(data)
.mount( .mount(
@@ -68,6 +71,7 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
client_server::get_avatar_url_route, client_server::get_avatar_url_route,
client_server::get_profile_route, client_server::get_profile_route,
client_server::set_presence_route, client_server::set_presence_route,
client_server::get_presence_route,
client_server::upload_keys_route, client_server::upload_keys_route,
client_server::get_keys_route, client_server::get_keys_route,
client_server::claim_keys_route, client_server::claim_keys_route,
@@ -149,6 +153,8 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
server_server::send_transaction_message_route, server_server::send_transaction_message_route,
server_server::get_event_route, server_server::get_event_route,
server_server::get_missing_events_route, server_server::get_missing_events_route,
server_server::get_event_authorization_route,
server_server::get_room_state_route,
server_server::get_room_state_ids_route, server_server::get_room_state_ids_route,
server_server::create_join_event_template_route, server_server::create_join_event_template_route,
server_server::create_join_event_route, server_server::create_join_event_route,
@@ -156,6 +162,8 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
server_server::get_devices_route, server_server::get_devices_route,
server_server::get_room_information_route, server_server::get_room_information_route,
server_server::get_profile_information_route, server_server::get_profile_information_route,
server_server::get_keys_route,
server_server::claim_keys_route,
], ],
) )
.register( .register(
@@ -193,8 +201,6 @@ async fn main() {
.await .await
.expect("config is valid"); .expect("config is valid");
db.sending.start_handler(&db);
if config.allow_jaeger { if config.allow_jaeger {
let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline() let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline()
.with_service_name("conduit") .with_service_name("conduit")
@@ -205,13 +211,16 @@ async fn main() {
let root = span!(tracing::Level::INFO, "app_start", work_units = 2); let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
let _enter = root.enter(); let _enter = root.enter();
} else {
std::env::set_var("CONDUIT_LOG", config.log);
pretty_env_logger::init_custom_env("CONDUIT_LOG");
}
let rocket = setup_rocket(raw_config, db); let rocket = setup_rocket(raw_config, db);
rocket.launch().await.unwrap(); rocket.launch().await.unwrap();
} else {
std::env::set_var("RUST_LOG", config.log);
tracing_subscriber::fmt::init();
let rocket = setup_rocket(raw_config, db);
rocket.launch().await.unwrap();
}
} }
#[catch(404)] #[catch(404)]
+14 -12
View File
@@ -2,15 +2,17 @@ use crate::Error;
use log::error; use log::error;
use ruma::{ use ruma::{
events::{ events::{
pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent, pdu::EventHash, room::member::MemberEventContent, AnyEphemeralRoomEvent, AnyRoomEvent,
AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, StateEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType,
StateEvent,
}, },
serde::{CanonicalJsonObject, CanonicalJsonValue, Raw}, serde::{CanonicalJsonObject, CanonicalJsonValue, Raw},
EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId, state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName,
ServerSigningKeyId, UInt, UserId,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH}; use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom};
#[derive(Clone, Deserialize, Serialize, Debug)] #[derive(Clone, Deserialize, Serialize, Debug)]
pub struct PduEvent { pub struct PduEvent {
@@ -72,9 +74,7 @@ impl PduEvent {
self.unsigned.insert( self.unsigned.insert(
"redacted_because".to_owned(), "redacted_because".to_owned(),
serde_json::to_string(reason) serde_json::to_value(reason).expect("to_value(PduEvent) always works"),
.expect("PduEvent::to_string always works")
.into(),
); );
self.content = new_content.into(); self.content = new_content.into();
@@ -105,7 +105,7 @@ impl PduEvent {
/// This only works for events that are also AnyRoomEvents. /// This only works for events that are also AnyRoomEvents.
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn to_any_event(&self) -> Raw<AnyEvent> { pub fn to_any_event(&self) -> Raw<AnyEphemeralRoomEvent> {
let mut json = json!({ let mut json = json!({
"content": self.content, "content": self.content,
"type": self.kind, "type": self.kind,
@@ -213,7 +213,10 @@ impl PduEvent {
pub fn convert_to_outgoing_federation_event( pub fn convert_to_outgoing_federation_event(
mut pdu_json: CanonicalJsonObject, mut pdu_json: CanonicalJsonObject,
) -> Raw<ruma::events::pdu::Pdu> { ) -> Raw<ruma::events::pdu::Pdu> {
if let Some(CanonicalJsonValue::Object(unsigned)) = pdu_json.get_mut("unsigned") { if let Some(unsigned) = pdu_json
.get_mut("unsigned")
.and_then(|val| val.as_object_mut())
{
unsigned.remove("transaction_id"); unsigned.remove("transaction_id");
} }
@@ -264,10 +267,9 @@ impl state_res::Event for PduEvent {
fn content(&self) -> serde_json::Value { fn content(&self) -> serde_json::Value {
self.content.clone() self.content.clone()
} }
fn origin_server_ts(&self) -> std::time::SystemTime { fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
UNIX_EPOCH + std::time::Duration::from_millis(self.origin_server_ts.into()) MilliSecondsSinceUnixEpoch(self.origin_server_ts)
} }
fn state_key(&self) -> Option<String> { fn state_key(&self) -> Option<String> {
self.state_key.clone() self.state_key.clone()
} }
+128 -84
View File
@@ -1,14 +1,15 @@
use crate::Error; use crate::Error;
use ruma::{ use ruma::{
api::OutgoingResponse, api::{client::r0::uiaa::UiaaResponse, OutgoingResponse},
identifiers::{DeviceId, UserId}, identifiers::{DeviceId, UserId},
Outgoing, signatures::CanonicalJsonValue,
Outgoing, ServerName,
}; };
use std::ops::Deref; use std::ops::Deref;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use { use {
crate::{server_server, utils}, crate::{server_server, Database},
log::{debug, warn}, log::{debug, warn},
rocket::{ rocket::{
data::{self, ByteUnit, Data, FromData}, data::{self, ByteUnit, Data, FromData},
@@ -18,14 +19,11 @@ use {
tokio::io::AsyncReadExt, tokio::io::AsyncReadExt,
Request, State, Request, State,
}, },
ruma::{ ruma::api::{AuthScheme, IncomingRequest},
api::{AuthScheme, IncomingRequest},
signatures::CanonicalJsonValue,
ServerName,
},
std::collections::BTreeMap, std::collections::BTreeMap,
std::convert::TryFrom, std::convert::TryFrom,
std::io::Cursor, std::io::Cursor,
std::sync::Arc,
}; };
/// This struct converts rocket requests into ruma structs by converting them into http requests /// This struct converts rocket requests into ruma structs by converting them into http requests
@@ -34,8 +32,9 @@ pub struct Ruma<T: Outgoing> {
pub body: T::Incoming, pub body: T::Incoming,
pub sender_user: Option<UserId>, pub sender_user: Option<UserId>,
pub sender_device: Option<Box<DeviceId>>, pub sender_device: Option<Box<DeviceId>>,
pub sender_servername: Option<Box<ServerName>>,
// This is None when body is not a valid string // This is None when body is not a valid string
pub json_body: Option<Box<serde_json::value::RawValue>>, pub json_body: Option<CanonicalJsonValue>,
pub from_appservice: bool, pub from_appservice: bool,
} }
@@ -50,7 +49,7 @@ where
async fn from_data(request: &'a Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> { async fn from_data(request: &'a Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
let metadata = T::Incoming::METADATA; let metadata = T::Incoming::METADATA;
let db = request let db = request
.guard::<State<'_, crate::Database>>() .guard::<State<'_, Arc<Database>>>()
.await .await
.expect("database was loaded"); .expect("database was loaded");
@@ -58,7 +57,7 @@ where
let token = request let token = request
.headers() .headers()
.get_one("Authorization") .get_one("Authorization")
.map(|s| s[7..].to_owned()) // Split off "Bearer " .and_then(|s| s.get(7..)) // Split off "Bearer "
.or_else(|| request.query_value("access_token").and_then(|r| r.ok())); .or_else(|| request.query_value("access_token").and_then(|r| r.ok()));
let limit = db.globals.max_request_size(); let limit = db.globals.max_request_size();
@@ -66,9 +65,15 @@ where
let mut body = Vec::new(); let mut body = Vec::new();
handle.read_to_end(&mut body).await.unwrap(); handle.read_to_end(&mut body).await.unwrap();
let (sender_user, sender_device, from_appservice) = if let Some((_id, registration)) = db let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
let (sender_user, sender_device, sender_servername, from_appservice) = if let Some((
_id,
registration,
)) = db
.appservice .appservice
.iter_all() .iter_all()
.unwrap()
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.find(|(_id, registration)| { .find(|(_id, registration)| {
registration registration
@@ -102,10 +107,10 @@ where
} }
// TODO: Check if appservice is allowed to be that user // TODO: Check if appservice is allowed to be that user
(Some(user_id), None, true) (Some(user_id), None, None, true)
} }
AuthScheme::ServerSignatures => (None, None, true), AuthScheme::ServerSignatures => (None, None, None, true),
AuthScheme::None => (None, None, true), AuthScheme::None => (None, None, None, true),
} }
} else { } else {
match metadata.authentication { match metadata.authentication {
@@ -114,9 +119,12 @@ where
match db.users.find_from_token(&token).unwrap() { match db.users.find_from_token(&token).unwrap() {
// Unknown Token // Unknown Token
None => return Failure((Status::raw(581), ())), None => return Failure((Status::raw(581), ())),
Some((user_id, device_id)) => { Some((user_id, device_id)) => (
(Some(user_id), Some(device_id.into()), false) Some(user_id),
} Some(Box::<DeviceId>::from(device_id)),
None,
false,
),
} }
} else { } else {
// Missing Token // Missing Token
@@ -125,16 +133,18 @@ where
} }
AuthScheme::ServerSignatures => { AuthScheme::ServerSignatures => {
// Get origin from header // Get origin from header
let x_matrix = match request.headers().get_one("Authorization").map(|s| { let x_matrix = match request
// Split off "X-Matrix " and parse the rest .headers()
s[9..] .get_one("Authorization")
.split_terminator(',') .and_then(|s| s.get(9..)) // Split off "X-Matrix " and parse the rest
.map(|field| { .map(|s| {
let mut splits = field.splitn(2, '='); s.split_terminator(',')
(splits.next(), splits.next().map(|s| s.trim_matches('"'))) .map(|field| {
}) let mut splits = field.splitn(2, '=');
.collect::<BTreeMap<_, _>>() (splits.next(), splits.next().map(|s| s.trim_matches('"')))
}) { })
.collect::<BTreeMap<_, _>>()
}) {
Some(t) => t, Some(t) => t,
None => { None => {
warn!("No Authorization header"); warn!("No Authorization header");
@@ -187,12 +197,10 @@ where
} }
}; };
let json_body = serde_json::from_slice::<CanonicalJsonValue>(&body);
let mut request_map = BTreeMap::<String, CanonicalJsonValue>::new(); let mut request_map = BTreeMap::<String, CanonicalJsonValue>::new();
if let Ok(json_body) = json_body { if let Some(json_body) = &json_body {
request_map.insert("content".to_owned(), json_body); request_map.insert("content".to_owned(), json_body.clone());
}; };
request_map.insert( request_map.insert(
@@ -227,27 +235,24 @@ where
CanonicalJsonValue::Object(signatures), CanonicalJsonValue::Object(signatures),
); );
let keys = match server_server::fetch_signing_keys( let keys =
&db, match server_server::fetch_signing_keys(&db, &origin, vec![key.to_owned()])
&origin, .await
vec![&key.to_owned()], {
) Ok(b) => b,
.await Err(e) => {
{ warn!("Failed to fetch signing keys: {}", e);
Ok(b) => b,
Err(e) => {
warn!("Failed to fetch signing keys: {}", e);
// Forbidden // Forbidden
return Failure((Status::raw(580), ())); return Failure((Status::raw(580), ()));
} }
}; };
let mut pub_key_map = BTreeMap::new(); let mut pub_key_map = BTreeMap::new();
pub_key_map.insert(origin.as_str().to_owned(), keys); pub_key_map.insert(origin.as_str().to_owned(), keys);
match ruma::signatures::verify_json(&pub_key_map, &request_map) { match ruma::signatures::verify_json(&pub_key_map, &request_map) {
Ok(()) => (None, None, false), Ok(()) => (None, None, Some(origin), false),
Err(e) => { Err(e) => {
warn!("Failed to verify json request from {}: {}", origin, e); warn!("Failed to verify json request from {}: {}", origin, e);
@@ -260,7 +265,7 @@ where
} }
} }
} }
AuthScheme::None => (None, None, false), AuthScheme::None => (None, None, None, false),
} }
}; };
@@ -271,6 +276,35 @@ where
http_request = http_request.header(header.name.as_str(), &*header.value); http_request = http_request.header(header.name.as_str(), &*header.value);
} }
if let Some(json_body) = json_body.as_mut().and_then(|val| val.as_object_mut()) {
let user_id = sender_user.clone().unwrap_or_else(|| {
UserId::parse_with_server_name("", db.globals.server_name())
.expect("we know this is valid")
});
if let Some(CanonicalJsonValue::Object(initial_request)) = json_body
.get("auth")
.and_then(|auth| auth.as_object())
.and_then(|auth| auth.get("session"))
.and_then(|session| session.as_str())
.and_then(|session| {
db.uiaa
.get_uiaa_request(
&user_id,
&sender_device.clone().unwrap_or_else(|| "".into()),
session,
)
.ok()
.flatten()
})
{
for (key, value) in initial_request {
json_body.entry(key).or_insert(value);
}
}
body = serde_json::to_vec(json_body).expect("value to bytes can't fail");
}
let http_request = http_request.body(&*body).unwrap(); let http_request = http_request.body(&*body).unwrap();
debug!("{:?}", http_request); debug!("{:?}", http_request);
match <T::Incoming as IncomingRequest>::try_from_http_request(http_request) { match <T::Incoming as IncomingRequest>::try_from_http_request(http_request) {
@@ -278,14 +312,13 @@ where
body: t, body: t,
sender_user, sender_user,
sender_device, sender_device,
// TODO: Can we avoid parsing it again? (We only need this for append_pdu) sender_servername,
json_body: utils::string_from_bytes(&body)
.ok()
.and_then(|s| serde_json::value::RawValue::from_string(s).ok()),
from_appservice, from_appservice,
json_body,
}), }),
Err(e) => { Err(e) => {
warn!("{:?}", e); warn!("{:?}", e);
// Bad Json
Failure((Status::raw(583), ())) Failure((Status::raw(583), ()))
} }
} }
@@ -303,49 +336,60 @@ impl<T: Outgoing> Deref for Ruma<T> {
/// This struct converts ruma responses into rocket http responses. /// This struct converts ruma responses into rocket http responses.
pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>; pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>;
pub struct RumaResponse<T: OutgoingResponse>(pub T); pub fn response<T: OutgoingResponse>(response: RumaResponse<T>) -> response::Result<'static> {
let http_response = response
.0
.try_into_http_response::<Vec<u8>>()
.map_err(|_| Status::InternalServerError)?;
impl<T: OutgoingResponse> From<T> for RumaResponse<T> { let mut response = rocket::response::Response::build();
let status = http_response.status();
response.raw_status(status.into(), "");
for header in http_response.headers() {
response.raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
}
let http_body = http_response.into_body();
response.sized_body(http_body.len(), Cursor::new(http_body));
response.raw_header("Access-Control-Allow-Origin", "*");
response.raw_header(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
response.raw_header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
);
response.raw_header("Access-Control-Max-Age", "86400");
response.ok()
}
#[derive(Clone)]
pub struct RumaResponse<T>(pub T);
impl<T> From<T> for RumaResponse<T> {
fn from(t: T) -> Self { fn from(t: T) -> Self {
Self(t) Self(t)
} }
} }
impl From<Error> for RumaResponse<UiaaResponse> {
fn from(t: Error) -> Self {
t.to_response()
}
}
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T> impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T>
where where
T: Send + OutgoingResponse,
'o: 'r, 'o: 'r,
T: OutgoingResponse,
{ {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
let http_response = self response(self)
.0
.try_into_http_response::<Vec<u8>>()
.map_err(|_| Status::InternalServerError)?;
let mut response = rocket::response::Response::build();
let status = http_response.status();
response.raw_status(status.into(), "");
for header in http_response.headers() {
response.raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
}
let http_body = http_response.into_body();
response.sized_body(http_body.len(), Cursor::new(http_body));
response.raw_header("Access-Control-Allow-Origin", "*");
response.raw_header(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
response.raw_header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
);
response.raw_header("Access-Control-Max-Age", "86400");
response.ok()
} }
} }
+721 -357
View File
File diff suppressed because it is too large Load Diff
+28 -24
View File
@@ -15,6 +15,15 @@ pub fn millis_since_unix_epoch() -> u64 {
.as_millis() as u64 .as_millis() as u64
} }
#[cfg(feature = "rocksdb")]
pub fn increment_rocksdb(
_new_key: &[u8],
old: Option<&[u8]>,
_operands: &mut rocksdb::MergeOperands,
) -> Option<Vec<u8>> {
increment(old)
}
pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> { pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
let number = match old.map(|bytes| bytes.try_into()) { let number = match old.map(|bytes| bytes.try_into()) {
Some(Ok(bytes)) => { Some(Ok(bytes)) => {
@@ -27,16 +36,14 @@ pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
Some(number.to_be_bytes().to_vec()) Some(number.to_be_bytes().to_vec())
} }
pub fn generate_keypair(old: Option<&[u8]>) -> Option<Vec<u8>> { pub fn generate_keypair() -> Vec<u8> {
Some(old.map(|s| s.to_vec()).unwrap_or_else(|| { let mut value = random_string(8).as_bytes().to_vec();
let mut value = random_string(8).as_bytes().to_vec(); value.push(0xff);
value.push(0xff); value.extend_from_slice(
value.extend_from_slice( &ruma::signatures::Ed25519KeyPair::generate()
&ruma::signatures::Ed25519KeyPair::generate() .expect("Ed25519KeyPair generation always works (?)"),
.expect("Ed25519KeyPair generation always works (?)"), );
); value
value
}))
} }
/// Parses the bytes into an u64. /// Parses the bytes into an u64.
@@ -77,22 +84,19 @@ pub fn common_elements(
let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>(); let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>();
Some(first_iterator.filter(move |target| { Some(first_iterator.filter(move |target| {
other_iterators other_iterators.iter_mut().all(|it| {
.iter_mut() while let Some(element) = it.peek() {
.map(|it| { match check_order(element, target) {
while let Some(element) = it.peek() { Ordering::Greater => return false, // We went too far
match check_order(element, target) { Ordering::Equal => return true, // Element is in both iters
Ordering::Greater => return false, // We went too far Ordering::Less => {
Ordering::Equal => return true, // Element is in both iters // Keep searching
Ordering::Less => { it.next();
// Keep searching
it.next();
}
} }
} }
false }
}) false
.all(|b| b) })
})) }))
} }