Compare commits

..
6 Commits
Author SHA1 Message Date
Jonas Platte 42a7a9f795 WIP 2021-04-29 20:50:09 +02:00
Jonas Platte 54a0f21347 WIP 2021-04-29 20:17:14 +02:00
Jonas Platte 54f4fd2cc8 Remove explicit setting of content-type header
`try_into_http_request` takes care of this already.
2021-04-29 20:16:55 +02:00
Jonas Platte ac5dd5e251 Fix formatting 2021-04-29 20:16:52 +02:00
Jonas Platte 6510ffa0fa Fix clippy warnings 2021-04-29 20:16:49 +02:00
Jonas Platte caa186ad22 Bump ruma 2021-04-29 20:15:51 +02:00
66 changed files with 2430 additions and 4358 deletions
-15
View File
@@ -1,28 +1,13 @@
image: "rust:latest"
default:
tags: [docker]
cache:
paths:
- target
- cargohome
variables:
GIT_SUBMODULE_STRATEGY: recursive
CARGO_HOME: "cargohome"
FF_USE_FASTZIP: 1
before_script:
- mkdir -p $CARGO_HOME && echo "using $CARGO_HOME to cache cargo deps"
- apt-get update -yqq
- apt-get install -yqq --no-install-recommends build-essential libssl-dev pkg-config
- rustup component add clippy rustfmt
test:cargo:
script:
- rustc --version && cargo --version # Print version info for debugging
- cargo test --workspace --verbose --locked
- cargo fmt --all -- --check
- cargo clippy
-48
View File
@@ -1,48 +0,0 @@
# 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
+115 -421
View File
@@ -42,6 +42,27 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002"
[[package]]
name = "async-stream"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a26cb53174ddd320edfff199a853f93d571f48eeb4dde75e67a9a3dbb7b7e5e"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db134ba52475c060f3329a8ef0f8786d6b872ed01515d4b79c162e5798da1340"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.50"
@@ -103,25 +124,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bitflags"
version = "1.2.1"
@@ -139,15 +141,6 @@ dependencies = [
"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]]
name = "bumpalo"
version = "3.6.1"
@@ -181,15 +174,6 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
@@ -215,17 +199,6 @@ dependencies = [
"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]]
name = "color_quant"
version = "1.1.0"
@@ -243,16 +216,14 @@ dependencies = [
"image",
"jsonwebtoken",
"log",
"lru-cache",
"opentelemetry",
"opentelemetry-jaeger",
"pretty_env_logger",
"rand 0.8.3",
"rand",
"regex",
"reqwest",
"ring",
"rocket",
"rocksdb",
"ruma",
"rust-argon2",
"rustls",
@@ -261,6 +232,7 @@ dependencies = [
"serde_json",
"serde_yaml",
"sled",
"state-res",
"thiserror",
"tokio",
"tracing",
@@ -270,12 +242,6 @@ dependencies = [
"webpki",
]
[[package]]
name = "const-oid"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279bc8fc53f788a75c7804af68237d1fce02cde1e275a886a4b320604dc2aeda"
[[package]]
name = "const_fn"
version = "0.4.7"
@@ -321,15 +287,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@@ -363,19 +320,6 @@ dependencies = [
"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]]
name = "data-encoding"
version = "2.3.2"
@@ -392,15 +336,6 @@ dependencies = [
"byteorder",
]
[[package]]
name = "der"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eeb9d92785d1facb50567852ce75d0858630630e7eabea59cf7eb7474051087"
dependencies = [
"const-oid",
]
[[package]]
name = "derive_more"
version = "0.99.13"
@@ -443,15 +378,6 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "directories"
version = "3.0.1"
@@ -484,29 +410,6 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "either"
version = "1.6.1"
@@ -701,16 +604,6 @@ dependencies = [
"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]]
name = "getrandom"
version = "0.1.16"
@@ -924,15 +817,6 @@ dependencies = [
"serde",
]
[[package]]
name = "indoc"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136"
dependencies = [
"unindent",
]
[[package]]
name = "inlinable_string"
version = "0.1.14"
@@ -1049,40 +933,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "linked-hash-map"
version = "0.5.4"
@@ -1227,16 +1083,6 @@ dependencies = [
"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]]
name = "ntapi"
version = "0.3.6"
@@ -1314,12 +1160,6 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl-probe"
version = "0.1.2"
@@ -1338,7 +1178,7 @@ dependencies = [
"lazy_static",
"percent-encoding",
"pin-project",
"rand 0.8.3",
"rand",
"thiserror",
]
@@ -1418,12 +1258,6 @@ dependencies = [
"syn",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pem"
version = "0.8.3"
@@ -1473,17 +1307,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9c2f795bc591cb3384cb64082a578b89207ac92bb89c9d98c1ea2ace7cd8110"
dependencies = [
"der",
"spki",
"zeroize",
]
[[package]]
name = "png"
version = "0.16.8"
@@ -1571,19 +1394,6 @@ dependencies = [
"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]]
name = "rand"
version = "0.8.3"
@@ -1591,19 +1401,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha 0.3.0",
"rand_core 0.6.2",
"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",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
@@ -1613,16 +1413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"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",
"rand_core",
]
[[package]]
@@ -1634,22 +1425,13 @@ dependencies = [
"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]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core 0.6.2",
"rand_core",
]
[[package]]
@@ -1814,7 +1596,7 @@ dependencies = [
"num_cpus",
"parking_lot",
"pin-project-lite",
"rand 0.8.3",
"rand",
"ref-cast",
"rocket_codegen",
"rocket_http",
@@ -1870,25 +1652,16 @@ dependencies = [
"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]]
name = "ruma"
version = "0.1.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.0.3"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"assign",
"js_int",
"ruma-api",
"ruma-appservice-api",
"ruma-client",
"ruma-client-api",
"ruma-common",
"ruma-events",
@@ -1898,13 +1671,12 @@ dependencies = [
"ruma-push-gateway-api",
"ruma-serde",
"ruma-signatures",
"ruma-state-res",
]
[[package]]
name = "ruma-api"
version = "0.17.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.17.0-alpha.4"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"bytes",
"http",
@@ -1919,8 +1691,8 @@ dependencies = [
[[package]]
name = "ruma-api-macros"
version = "0.17.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.17.0-alpha.4"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -1930,8 +1702,8 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.2.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.2.0-alpha.3"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"ruma-api",
"ruma-common",
@@ -1942,10 +1714,31 @@ dependencies = [
"serde_json",
]
[[package]]
name = "ruma-client"
version = "0.5.0-alpha.2"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"assign",
"async-stream",
"async-trait",
"bytes",
"futures-core",
"http",
"reqwest",
"ruma-api",
"ruma-common",
"ruma-events",
"ruma-identifiers",
"ruma-serde",
"serde",
"serde_json",
]
[[package]]
name = "ruma-client-api"
version = "0.10.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.10.0-alpha.3"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"assign",
"bytes",
@@ -1964,11 +1757,12 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.5.3"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.5.0"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"indexmap",
"js_int",
"maplit",
"ruma-identifiers",
"ruma-serde",
"serde",
@@ -1979,10 +1773,9 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.22.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.22.0-alpha.3"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"indoc",
"js_int",
"ruma-common",
"ruma-events-macros",
@@ -1994,8 +1787,8 @@ dependencies = [
[[package]]
name = "ruma-events-macros"
version = "0.22.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.22.0-alpha.3"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2005,8 +1798,8 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.1.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.1.0-alpha.2"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"js_int",
"ruma-api",
@@ -2020,11 +1813,11 @@ dependencies = [
[[package]]
name = "ruma-identifiers"
version = "0.19.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.19.0"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"paste",
"rand 0.8.3",
"rand",
"ruma-identifiers-macros",
"ruma-identifiers-validation",
"ruma-serde",
@@ -2034,9 +1827,10 @@ dependencies = [
[[package]]
name = "ruma-identifiers-macros"
version = "0.19.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.19.0"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"proc-macro2",
"quote",
"ruma-identifiers-validation",
"syn",
@@ -2044,13 +1838,13 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.4.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.3.0"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
[[package]]
name = "ruma-identity-service-api"
version = "0.1.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.1.0-alpha.1"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"js_int",
"ruma-api",
@@ -2058,12 +1852,13 @@ dependencies = [
"ruma-identifiers",
"ruma-serde",
"serde",
"serde_json",
]
[[package]]
name = "ruma-push-gateway-api"
version = "0.1.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.1.0-alpha.1"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"js_int",
"ruma-api",
@@ -2077,8 +1872,8 @@ dependencies = [
[[package]]
name = "ruma-serde"
version = "0.4.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.3.1"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"bytes",
"form_urlencoded",
@@ -2091,8 +1886,8 @@ dependencies = [
[[package]]
name = "ruma-serde-macros"
version = "0.4.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.3.1"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2102,41 +1897,17 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.7.2"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
version = "0.7.0"
source = "git+https://github.com/ruma/ruma?rev=097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2#097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2"
dependencies = [
"base64 0.13.0",
"ed25519-dalek",
"pkcs8",
"rand 0.7.3",
"ring",
"ruma-identifiers",
"ruma-serde",
"serde_json",
"sha2",
"thiserror",
"tracing",
"untrusted",
]
[[package]]
name = "ruma-state-res"
version = "0.1.0"
source = "git+https://github.com/ruma/ruma?rev=5a7e2cddcf257e367465cced51442c91e8f557c9#5a7e2cddcf257e367465cced51442c91e8f557c9"
dependencies = [
"itertools 0.10.0",
"js_int",
"maplit",
"ruma-common",
"ruma-events",
"ruma-identifiers",
"ruma-serde",
"ruma-signatures",
"serde",
"serde_json",
"thiserror",
"tracing",
]
[[package]]
name = "rust-argon2"
version = "0.8.3"
@@ -2149,12 +1920,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
@@ -2273,18 +2038,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.126"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
@@ -2332,19 +2097,6 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "sharded-slab"
version = "0.1.1"
@@ -2354,12 +2106,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
@@ -2369,12 +2115,6 @@ dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68"
[[package]]
name = "simple_asn1"
version = "0.4.1"
@@ -2442,15 +2182,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spki"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dae7e047abc519c96350e9484a96c6bf1492348af912fd3446dd2dc323f6268"
dependencies = [
"der",
]
[[package]]
name = "stable-pattern"
version = "0.1.0"
@@ -2477,6 +2208,20 @@ dependencies = [
"loom",
]
[[package]]
name = "state-res"
version = "0.1.0"
source = "git+https://github.com/ruma/state-res?rev=bca91bcc17df4d16942ff8b8c3be622b1795e3f0#bca91bcc17df4d16942ff8b8c3be622b1795e3f0"
dependencies = [
"itertools 0.10.0",
"log",
"maplit",
"ruma",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "stdweb"
version = "0.4.20"
@@ -2526,12 +2271,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "subtle"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.70"
@@ -2543,18 +2282,6 @@ dependencies = [
"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]]
name = "tempfile"
version = "3.2.0"
@@ -2563,7 +2290,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"libc",
"rand 0.8.3",
"rand",
"redox_syscall 0.2.6",
"remove_dir_all",
"winapi",
@@ -2764,9 +2491,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.26"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@@ -2787,9 +2514,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.18"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
dependencies = [
"lazy_static",
]
@@ -2867,7 +2594,7 @@ dependencies = [
"ipnet",
"lazy_static",
"log",
"rand 0.8.3",
"rand",
"smallvec",
"thiserror",
"tinyvec",
@@ -2911,12 +2638,6 @@ dependencies = [
"unchecked-index",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ubyte"
version = "0.10.1"
@@ -2972,12 +2693,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "unindent"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]]
name = "untrusted"
version = "0.7.1"
@@ -3194,27 +2909,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "zstd"
version = "0.5.4+zstd.1.4.7"
+12 -14
View File
@@ -8,7 +8,6 @@ repository = "https://gitlab.com/famedly/conduit"
readme = "README.md"
version = "0.1.0"
edition = "2018"
rust = "1.50"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,16 +15,21 @@ rust = "1.50"
# Used to handle requests
# 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/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] }
# Used for matrix spec type definitions and helpers
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 = { 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"] }
ruma = { git = "https://github.com/ruma/ruma", rev = "097f20c837fcbaef1d9c18c30d3ec5a24a6e4ec2", features = ["client-reqwest", "compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "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 = { path = "../ruma/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
# Used when doing state resolution
state-res = { git = "https://github.com/ruma/state-res", rev = "bca91bcc17df4d16942ff8b8c3be622b1795e3f0", 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
tokio = "1.2.0"
# Used for storing data permanently
sled = { version = "0.34.6", features = ["compression", "no_metrics"], optional = true }
rocksdb = { version = "0.16.0", features = ["multi-threaded-cf"], optional = true }
sled = { version = "0.34.6", features = ["compression", "no_metrics"] }
#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
@@ -73,12 +77,9 @@ tracing-subscriber = "0.2.16"
tracing-opentelemetry = "0.11.0"
opentelemetry-jaeger = "0.11.0"
pretty_env_logger = "0.4.0"
lru-cache = "0.1.2"
[features]
default = ["conduit_bin", "backend_sled"]
backend_sled = ["sled"]
backend_rocksdb = ["rocksdb"]
default = ["conduit_bin"]
conduit_bin = [] # TODO: add rocket to this when it is optional
[[bin]]
@@ -102,16 +103,13 @@ instead of a server that has high scalability."""
section = "net"
priority = "optional"
assets = [
["debian/env.local", "etc/matrix-conduit/local", "644"],
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
["README.md", "usr/share/doc/matrix-conduit/", "644"],
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
]
conf-files = [
"/etc/matrix-conduit/conduit.toml"
"/etc/matrix-conduit/local"
]
maintainer-scripts = "debian/"
systemd-units = { unit-name = "matrix-conduit" }
# For flamegraphs:
#[profile.release]
#debug = true
+2 -14
View File
@@ -6,7 +6,7 @@ If you run into any problems while setting up Conduit, write an email to `timo@k
## Installing Conduit
You may simply download the binary that fits your machine. Run `uname -m` to see
You have to download the binary that fits your machine. Run `uname -m` to see
what you need. Now copy the right url:
- x84_64: `https://conduit.rs/master/x86_64/conduit-bin`
- armv7: `https://conduit.rs/master/armv7/conduit-bin`
@@ -18,14 +18,6 @@ $ sudo wget -O /usr/local/bin/matrix-conduit <url>
$ 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
While Conduit can run as any user it is usually better to use dedicated users for different services.
@@ -104,10 +96,8 @@ allow_registration = false
allow_encryption = true
allow_federation = true
trusted_servers = ["matrix.org"]
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time
#workers = 4 # default: cpu core count * 2
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
@@ -217,5 +207,3 @@ Set it to start automatically when your system boots with:
```bash
$ sudo systemctl enable conduit
```
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
+3 -5
View File
@@ -30,7 +30,7 @@ COPY . .
RUN if [[ $LOCAL == "true" ]]; then \
cargo install --path . ; \
else \
cargo install --git "https://gitlab.com/famedly/conduit.git" --rev ${GIT_REF}; \
cargo install --git "https://github.com/timokoesters/conduit.git" --rev ${GIT_REF}; \
fi
########################## RUNTIME IMAGE ##########################
@@ -40,9 +40,7 @@ FROM alpine:3.12
ARG CREATED
ARG VERSION
ARG GIT_REF=origin/master
ENV CONDUIT_CONFIG="/srv/conduit/conduit.toml"
ARG GIT_REF=HEAD
# Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md
# including a custom label specifying the build command
@@ -54,7 +52,7 @@ LABEL org.opencontainers.image.created=${CREATED} \
org.opencontainers.image.description="A Matrix homeserver written in Rust" \
org.opencontainers.image.url="https://conduit.rs/" \
org.opencontainers.image.revision=${GIT_REF} \
org.opencontainers.image.source="https://gitlab.com/famedly/conduit.git" \
org.opencontainers.image.source="https://git.koesters.xyz/timo/conduit.git" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.documentation="" \
org.opencontainers.image.ref.name="" \
+2 -27
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.
#### What is it built on?
#### What is it build on?
- [Ruma](https://www.ruma.io): Useful structures for endpoint requests and
responses that can be (de)serialized
@@ -52,40 +52,15 @@ 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
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
Pull and run the docker image with
``` bash
docker pull 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
docker run -d -p 8448:8000 -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)
+2 -4
View File
@@ -11,7 +11,7 @@
# YOU NEED TO EDIT THIS
#server_name = "your.server.name"
# This is the only directory where Conduit will save its data
# This is the only directly where Conduit will save its data
database_path = "/var/lib/conduit/conduit.db"
# The port Conduit will be running on. You need to set up a reverse proxy in
@@ -33,10 +33,8 @@ max_request_size = 20_000_000 # in bytes
# Enable jaeger to support monitoring and troubleshooting through jaeger
#allow_jaeger = false
trusted_servers = ["matrix.org"]
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
#max_concurrent_requests = 4 # 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
+10 -10
View File
@@ -4,25 +4,25 @@ Conduit for Debian
Configuration
-------------
When installed, Debconf generates the configuration of the homeserver
(host)name, the address and port it listens on. This configuration ends up in
/etc/matrix-conduit/conduit.toml.
When installed, Debconf handles the configuration of the homeserver (host)name,
the address and port it listens on. These configuration variables end up in
/etc/matrix-conduit/debian.
You can tweak more detailed settings by uncommenting and setting the variables
in /etc/matrix-conduit/conduit.toml. This involves settings such as the maximum
file size for download/upload, enabling federation, etc.
in /etc/matrix-conduit/local. This involves settings such as the maximum file
size for download/upload, enabling federation, etc.
Running
-------
The package uses the matrix-conduit.service systemd unit file to start and
stop Conduit. It loads the configuration file mentioned above to set up the
stop Conduit. It loads the configuration files mentioned above to set up the
environment before running the server.
This package assumes by default that Conduit will be placed behind a reverse
proxy such as Apache or nginx. This default deployment entails just listening
on 127.0.0.1 and the free port 6167 and is reachable via a client using the URL
http://localhost:6167.
This package assumes by default that Conduit is placed behind a reverse proxy
such as Apache or nginx. This default deployment entails just listening on
127.0.0.1 and the free port 14004 and is reachable via a client using the URL
http://localhost:14004.
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
+33
View File
@@ -0,0 +1,33 @@
# 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"
+4 -1
View File
@@ -34,7 +34,10 @@ SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
StateDirectory=matrix-conduit
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
Environment="ROCKET_ENV=production"
Environment="ROCKET_DATABASE_PATH=/var/lib/matrix-conduit"
EnvironmentFile=/etc/matrix-conduit/debian
EnvironmentFile=/etc/matrix-conduit/local
ExecStart=/usr/sbin/matrix-conduit
Restart=on-failure
+38 -50
View File
@@ -4,8 +4,8 @@ set -e
. /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_CONFIG_FILE="${CONDUIT_CONFIG_PATH}/conduit.toml"
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit/conduit_db
CONDUIT_CONFIG_FILE="$CONDUIT_CONFIG_PATH/debian"
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
case "$1" in
configure)
@@ -13,7 +13,7 @@ case "$1" in
if ! getent passwd _matrix-conduit > /dev/null ; then
echo 'Adding system user for the Conduit Matrix homeserver' 1>&2
adduser --system --group --quiet \
--home "$CONDUIT_DATABASE_PATH" \
--home $CONDUIT_DATABASE_PATH \
--disabled-login \
--force-badname \
_matrix-conduit
@@ -25,60 +25,48 @@ case "$1" in
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
fi
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
# Write the debconf values in the config.
db_get matrix-conduit/hostname
CONDUIT_SERVER_NAME="$RET"
db_get matrix-conduit/address
CONDUIT_ADDRESS="$RET"
db_get matrix-conduit/port
CONDUIT_PORT="$RET"
mkdir -p "$CONDUIT_CONFIG_PATH"
cat > "$CONDUIT_CONFIG_FILE" << EOF
[global]
# The server_name is the name of this server. It is used as a suffix for user
# and room ids. Examples: matrix.org, conduit.rs
# 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}"
# Write the debconf values in the config.
db_get matrix-conduit/hostname
ROCKET_SERVER_NAME="$RET"
db_get matrix-conduit/address
ROCKET_ADDRESS="$RET"
db_get matrix-conduit/port
ROCKET_PORT="$RET"
cat >"$CONDUIT_CONFIG_FILE" << EOF
# Conduit homeserver Debian configuration
#
# Conduit is an application based on the Rocket web framework.
# Configuration of Conduit happens via Debconf (of which the resulting config
# is in this file) and optionally by uncommenting and tweaking the variables in
# /etc/matrix-conduit/local.
# This is the only directory where Conduit will save its data.
database_path = "${CONDUIT_DATABASE_PATH}"
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN!
#
# Please make changes by running:
#
# \$ dpkg-reconfigure matrix-conduit
#
# or by providing overriding changes in /etc/matrix-conduit/local.
# The address Conduit will be listening on.
# The server (host)name of the Matrix homeserver.
#
# 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
# only listen on the localhost when using a reverse proxy.
address = "${CONDUIT_ADDRESS}"
ROCKET_ADDRESS="$ROCKET_ADDRESS"
# 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
# 443 and 8448 will be forwarded to the Conduit instance running on this port.
port = ${CONDUIT_PORT}
# The port of the Matrix homeserver.
#
# This port is could be any available port if accessed by a reverse proxy.
# By default the server listens on port 8000.
ROCKET_PORT="$ROCKET_PORT"
# 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
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN!
EOF
fi
;;
esac
-5
View File
@@ -1,16 +1,11 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
case $1 in
purge)
# Remove debconf changes from the db
db_purge
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
# "configuration files must be preserved when the package is removed, and
# 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
Type: string
Default: 6167
Default: 14004
Description: The port of the Matrix homeserver
This port is most often just accessed by a reverse proxy.
+21 -26
View File
@@ -5,48 +5,43 @@ services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### 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
### 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
build:
context: .
args:
CREATED: '2021-03-16T08:18:27Z'
VERSION: '0.1.0'
LOCAL: 'false'
GIT_REF: origin/master
# build:
# context: .
# args:
# CREATED:
# VERSION:
# LOCAL: 'false'
# GIT_REF: HEAD
restart: unless-stopped
ports:
- 8448:8000
volumes:
- db:/srv/conduit/.local/share/conduit
### Uncomment if you want to use conduit.toml to configure Conduit
### Note: Set env vars will override conduit.toml values
# - ./conduit.toml:/srv/conduit/conduit.toml
### Uncomment if you want to use Rocket.toml to configure Conduit
### Note: Set env vars will override Rocket.toml values
# - ./Rocket.toml:/srv/conduit/Rocket.toml
environment:
CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name
### Uncomment and change values as desired
# CONDUIT_ADDRESS: 127.0.0.1
# CONDUIT_PORT: 8000
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if you want to configure purely by env vars, set this to an empty string ''
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
# CONDUIT_ALLOW_JAEGER: 'false'
# 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
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical
# ROCKET_PORT: 8000
# ROCKET_REGISTRATION_DISABLED: 'true'
# ROCKET_ENCRYPTION_DISABLED: 'true'
# ROCKET_FEDERATION_ENABLED: 'true'
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit
# ROCKET_WORKERS: 10
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
### 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
### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
# image: vectorim/riot-web:latest
# restart: unless-stopped
# ports:
# - 8009:80
+4 -6
View File
@@ -17,13 +17,13 @@ The Dockerfile includes a few build arguments that should be supplied when build
ARG LOCAL=false
ARG CREATED
ARG VERSION
ARG GIT_REF=origin/master
ARG GIT_REF=HEAD
```
- **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)`
- **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 `origin/master`.
- **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`.
To build the image you can use the following command
@@ -40,12 +40,10 @@ which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`
After building the image you can simply run it with
``` bash
docker run -d -p 8448:8000 -v ~/conduit.toml:/srv/conduit/conduit.toml -v db:/srv/conduit/.local/share/conduit matrixconduit/matrix-conduit:latest
docker run -d -p 8448:8000 -v db:/srv/conduit/.local/share/conduit -e ROCKET_SERVER_NAME="localhost:8000" matrixconduit/matrix-conduit:latest
```
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.
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.
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.
+13 -19
View File
@@ -15,38 +15,32 @@ services:
# CREATED:
# VERSION:
# LOCAL: 'false'
# GIT_REF: origin/master
# GIT_REF: HEAD
restart: unless-stopped
volumes:
- db:/srv/conduit/.local/share/conduit
### Uncomment if you want to use conduit.toml to configure Conduit
### Note: Set env vars will override conduit.toml values
# - ./conduit.toml:/srv/conduit/conduit.toml
### Uncomment if you want to use Rocket.toml to configure Conduit
### Note: Set env vars will override Rocket.toml values
# - ./Rocket.toml:/srv/conduit/Rocket.toml
networks:
- proxy
environment:
CONDUIT_SERVER_NAME: localhost:8000 # replace with your own name
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
ROCKET_SERVER_NAME: localhost:8000 # replace with your own name
### Uncomment and change values as desired
# CONDUIT_ADDRESS: 127.0.0.1
# CONDUIT_PORT: 8000
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if want to configure purely by env vars, set this to an empty string ''
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# CONDUIT_LOG: info # default is: "info,rocket=off,_=off,sled=off"
# CONDUIT_ALLOW_JAEGER: 'false'
# 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
# ROCKET_LOG: normal # Available levels are: off, debug, normal, critical
# ROCKET_PORT: 8000
# ROCKET_REGISTRATION_DISABLED: 'true'
# ROCKET_ENCRYPTION_DISABLED: 'true'
# ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit
# ROCKET_WORKERS: 10
# ROCKET_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
### 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
### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
# image: vectorim/riot-web:latest
# restart: unless-stopped
# volumes:
# - ./element_config.json:/app/config.json
+44 -36
View File
@@ -1,11 +1,13 @@
use crate::{utils, Error, Result};
use bytes::BytesMut;
use log::warn;
use ruma::api::{IncomingResponse, OutgoingRequest, SendAccessToken};
use ruma::{
api::{IncomingResponse, OutgoingRequest, SendAccessToken},
client::HttpClientExt,
};
use std::{
convert::{TryFrom, TryInto},
fmt::Debug,
mem,
time::Duration,
};
@@ -20,47 +22,53 @@ where
let destination = registration.get("url").unwrap().as_str().unwrap();
let hs_token = registration.get("hs_token").unwrap().as_str().unwrap();
let mut http_request = request
.try_into_http_request::<BytesMut>(&destination, SendAccessToken::IfRequired(""))
.unwrap()
.map(|body| body.freeze());
globals
.reqwest_client()
.send_customized_matrix_request(
&destination,
SendAccessToken::IfRequired(""),
request,
|http_request| {
let mut parts = http_request.uri().clone().into_parts();
let old_path_and_query = parts.path_and_query.unwrap().as_str().to_owned();
let symbol = if old_path_and_query.contains('?') {
"&"
} else {
"?"
};
let mut parts = http_request.uri().clone().into_parts();
let old_path_and_query = parts.path_and_query.unwrap().as_str().to_owned();
let symbol = if old_path_and_query.contains('?') {
"&"
} else {
"?"
};
parts.path_and_query =
Some((old_path_and_query + symbol + "access_token=" + hs_token).parse()?);
*http_request.uri_mut() = parts.try_into()?;
parts.path_and_query = Some(
(old_path_and_query + symbol + "access_token=" + hs_token)
.parse()
.unwrap(),
);
*http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid");
let mut reqwest_request = reqwest::Request::try_from(http_request)
.expect("all http requests are valid reqwest requests");
Ok(())
},
)
.await
.map_err(|e| {
warn!("Appservice error: {}", e);
Error::BadServerResponse("Server returned an error.")
})?;
*reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
let url = reqwest_request.url().clone();
let mut response = globals.reqwest_client().execute(reqwest_request).await?;
let mut reqwest_response = globals.reqwest_client().execute(reqwest_request).await?;
// reqwest::Response -> http::Response conversion
let status = response.status();
let mut http_response_builder = http::Response::builder()
.status(status)
.version(response.version());
mem::swap(
response.headers_mut(),
http_response_builder
.headers_mut()
.expect("http::response::Builder is usable"),
);
// Because reqwest::Response -> http::Response is complicated:
let status = reqwest_response.status();
let mut http_response = http::Response::builder().status(status);
let headers = http_response.headers_mut().unwrap();
let body = response.bytes().await.unwrap_or_else(|e| {
for (k, v) in reqwest_response.headers_mut().drain() {
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);
Vec::new().into()
}); // TODO: handle timeout
@@ -76,7 +84,7 @@ where
}
let response = T::IncomingResponse::try_from_http_response(
http_response_builder
http_response
.body(body)
.expect("reqwest body is valid http body"),
);
+30 -42
View File
@@ -1,4 +1,4 @@
use std::{collections::BTreeMap, convert::TryInto, sync::Arc};
use std::{collections::BTreeMap, convert::TryInto};
use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
@@ -42,7 +42,7 @@ const GUEST_NAME_LENGTH: usize = 10;
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_register_available_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_username_availability::Request<'_>>,
) -> ConduitResult<get_username_availability::Response> {
// Validate user id
@@ -85,10 +85,10 @@ pub async fn get_register_available_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn register_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<register::Request<'_>>,
) -> ConduitResult<register::Response> {
if !db.globals.allow_registration() && !body.from_appservice {
if !db.globals.allow_registration() {
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Registration has been disabled.",
@@ -143,28 +143,16 @@ pub async fn register_route(
if !body.from_appservice {
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = db.uiaa.try_auth(
&UserId::parse_with_server_name("", db.globals.server_name())
.expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
&db.users,
&db.globals,
)?;
let (worked, uiaainfo) =
db.uiaa
.try_auth(&user_id, "".into(), auth, &uiaainfo, &db.users, &db.globals)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
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"),
)?;
db.uiaa.create(&user_id, "".into(), &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
}
@@ -179,11 +167,12 @@ pub async fn register_route(
let password = if is_guest {
None
} else {
body.password.as_deref()
};
body.password.clone()
}
.unwrap_or_default();
// Create user
db.users.create(&user_id, password)?;
db.users.create(&user_id, &password)?;
// Initial data
db.account_data.update(
@@ -227,12 +216,12 @@ pub async fn register_route(
)?;
// 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
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is valid");
db.users.create(&conduit_user, None)?;
db.users.create(&conduit_user, "")?;
let room_id = RoomId::new(db.globals.server_name());
@@ -286,8 +275,18 @@ pub async fn register_route(
event_type: EventType::RoomPowerLevels,
content: 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,
..Default::default()
users_default: 0.into(),
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
room: 50.into(),
},
},
)
.expect("event is valid, we just created it"),
@@ -496,7 +495,7 @@ pub async fn register_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn change_password_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<change_password::Request<'_>>,
) -> ConduitResult<change_password::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -527,17 +526,11 @@ pub async fn change_password_route(
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
db.users
.set_password(&sender_user, Some(&body.new_password))?;
db.users.set_password(&sender_user, &body.new_password)?;
if body.logout_devices {
// Logout all devices except the current one
@@ -588,7 +581,7 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::
)]
#[tracing::instrument(skip(db, body))]
pub async fn deactivate_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<deactivate::Request<'_>>,
) -> ConduitResult<deactivate::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -619,12 +612,7 @@ pub async fn deactivate_route(
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
+11 -8
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use regex::Regex;
@@ -24,7 +22,7 @@ use rocket::{delete, get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn create_alias_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_alias::Request<'_>>,
) -> ConduitResult<create_alias::Response> {
if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
@@ -45,7 +43,7 @@ pub async fn create_alias_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_alias_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_alias::Request<'_>>,
) -> ConduitResult<delete_alias::Response> {
db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
@@ -61,7 +59,7 @@ pub async fn delete_alias_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_alias_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_alias::Request<'_>>,
) -> ConduitResult<get_alias::Response> {
get_alias_helper(&db, &body.room_alias).await
@@ -88,8 +86,7 @@ pub async fn get_alias_helper(
match db.rooms.id_from_alias(&room_alias)? {
Some(r) => room_id = Some(r),
None => {
let iter = db.appservice.iter_all()?;
for (_id, registration) in iter.filter_map(|r| r.ok()) {
for (_id, registration) in db.appservice.iter_all().filter_map(|r| r.ok()) {
let aliases = registration
.get("namespaces")
.and_then(|ns| ns.get("aliases"))
@@ -97,7 +94,13 @@ pub async fn get_alias_helper(
.map_or_else(Vec::new, |aliases| {
aliases
.iter()
.filter_map(|aliases| Regex::new(aliases.get("regex")?.as_str()?).ok())
.map(|aliases| {
aliases
.get("regex")
.and_then(|regex| regex.as_str())
.and_then(|regex| Regex::new(regex).ok())
})
.filter_map(|o| o)
.collect::<Vec<_>>()
});
+15 -17
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{
@@ -21,7 +19,7 @@ use rocket::{delete, get, post, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn create_backup_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_backup::Request>,
) -> ConduitResult<create_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -40,7 +38,7 @@ pub async fn create_backup_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn update_backup_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<update_backup::Request<'_>>,
) -> ConduitResult<update_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -58,7 +56,7 @@ pub async fn update_backup_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_latest_backup_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_latest_backup::Request>,
) -> ConduitResult<get_latest_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -86,7 +84,7 @@ pub async fn get_latest_backup_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_backup_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_backup::Request<'_>>,
) -> ConduitResult<get_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -113,7 +111,7 @@ pub async fn get_backup_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_backup_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_backup::Request<'_>>,
) -> ConduitResult<delete_backup::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -132,7 +130,7 @@ pub async fn delete_backup_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn add_backup_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<add_backup_keys::Request<'_>>,
) -> ConduitResult<add_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -166,7 +164,7 @@ pub async fn add_backup_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn add_backup_key_sessions_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<add_backup_key_sessions::Request<'_>>,
) -> ConduitResult<add_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -198,7 +196,7 @@ pub async fn add_backup_key_sessions_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn add_backup_key_session_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<add_backup_key_session::Request<'_>>,
) -> ConduitResult<add_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -227,7 +225,7 @@ pub async fn add_backup_key_session_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_backup_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_backup_keys::Request<'_>>,
) -> ConduitResult<get_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -243,14 +241,14 @@ pub async fn get_backup_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_backup_key_sessions_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_backup_key_sessions::Request<'_>>,
) -> ConduitResult<get_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sessions = db
.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())
}
@@ -261,7 +259,7 @@ pub async fn get_backup_key_sessions_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_backup_key_session_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_backup_key_session::Request<'_>>,
) -> ConduitResult<get_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -283,7 +281,7 @@ pub async fn get_backup_key_session_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_backup_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_backup_keys::Request<'_>>,
) -> ConduitResult<delete_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -306,7 +304,7 @@ pub async fn delete_backup_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_backup_key_sessions_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_backup_key_sessions::Request<'_>>,
) -> ConduitResult<delete_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -329,7 +327,7 @@ pub async fn delete_backup_key_sessions_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_backup_key_session_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_backup_key_session::Request<'_>>,
) -> ConduitResult<delete_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+20 -29
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{
@@ -10,11 +8,11 @@ use ruma::{
set_room_account_data,
},
},
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
events::{custom::CustomEventContent, AnyBasicEventContent, BasicEvent},
serde::Raw,
};
use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue};
use serde_json::value::RawValue as RawJsonValue;
#[cfg(feature = "conduit_bin")]
use rocket::{get, put};
@@ -25,12 +23,12 @@ use rocket::{get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_global_account_data_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_global_account_data::Request<'_>>,
) -> ConduitResult<set_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str::<serde_json::Value>(body.data.get())
let data = serde_json::from_str(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
@@ -39,10 +37,9 @@ pub async fn set_global_account_data_route(
None,
sender_user,
event_type.clone().into(),
&json!({
"type": event_type,
"content": data,
}),
&BasicEvent {
content: CustomEventContent { event_type, data },
},
&db.globals,
)?;
@@ -60,12 +57,12 @@ pub async fn set_global_account_data_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_room_account_data_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_room_account_data::Request<'_>>,
) -> ConduitResult<set_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str::<serde_json::Value>(body.data.get())
let data = serde_json::from_str(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
@@ -74,10 +71,9 @@ pub async fn set_room_account_data_route(
Some(&body.room_id),
sender_user,
event_type.clone().into(),
&json!({
"type": event_type,
"content": data,
}),
&BasicEvent {
content: CustomEventContent { event_type, data },
},
&db.globals,
)?;
@@ -92,7 +88,7 @@ pub async fn set_room_account_data_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_global_account_data_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_global_account_data::Request<'_>>,
) -> ConduitResult<get_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -103,8 +99,8 @@ pub async fn get_global_account_data_route(
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?;
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
let account_data = serde_json::from_str::<ExtractEventContent>(event.get())
.expect("event to contain a content field")
.content;
Ok(get_global_account_data::Response { account_data }.into())
@@ -119,7 +115,7 @@ pub async fn get_global_account_data_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_room_account_data_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_room_account_data::Request<'_>>,
) -> ConduitResult<get_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -134,19 +130,14 @@ pub async fn get_room_account_data_route(
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?;
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
let account_data = serde_json::from_str::<ExtractEventContent>(event.get())
.expect("event to contain a content field")
.content;
Ok(get_room_account_data::Response { account_data }.into())
}
#[derive(Deserialize)]
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
struct ExtractEventContent {
content: Raw<AnyBasicEventContent>,
}
+2 -2
View File
@@ -1,7 +1,7 @@
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{error::ErrorKind, r0::context::get_context};
use std::{convert::TryFrom, sync::Arc};
use std::convert::TryFrom;
#[cfg(feature = "conduit_bin")]
use rocket::get;
@@ -12,7 +12,7 @@ use rocket::get;
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_context_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_context::Request<'_>>,
) -> ConduitResult<get_context::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+7 -19
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{utils, ConduitResult, Database, Error, Ruma};
use ruma::api::client::{
@@ -20,7 +18,7 @@ use rocket::{delete, get, post, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_devices_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_devices::Request>,
) -> ConduitResult<get_devices::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -40,7 +38,7 @@ pub async fn get_devices_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_device_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_device::Request<'_>>,
) -> ConduitResult<get_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -59,7 +57,7 @@ pub async fn get_device_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn update_device_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<update_device::Request<'_>>,
) -> ConduitResult<update_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -85,7 +83,7 @@ pub async fn update_device_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_device_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_device::Request<'_>>,
) -> ConduitResult<delete_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -117,12 +115,7 @@ pub async fn delete_device_route(
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
@@ -139,7 +132,7 @@ pub async fn delete_device_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_devices_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_devices::Request<'_>>,
) -> ConduitResult<delete_devices::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -171,12 +164,7 @@ pub async fn delete_devices_route(
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
+20 -47
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Result, Ruma};
use log::info;
@@ -35,7 +33,7 @@ use rocket::{get, post, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_public_rooms_filtered_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_public_rooms_filtered::Request<'_>>,
) -> ConduitResult<get_public_rooms_filtered::Response> {
get_public_rooms_filtered_helper(
@@ -55,7 +53,7 @@ pub async fn get_public_rooms_filtered_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_public_rooms_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_public_rooms::Request<'_>>,
) -> ConduitResult<get_public_rooms::Response> {
let response = get_public_rooms_filtered_helper(
@@ -84,7 +82,7 @@ pub async fn get_public_rooms_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_room_visibility_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_room_visibility::Request<'_>>,
) -> ConduitResult<set_room_visibility::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -114,7 +112,7 @@ pub async fn set_room_visibility_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_room_visibility_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_room_visibility::Request<'_>>,
) -> ConduitResult<get_room_visibility::Response> {
Ok(get_room_visibility::Response {
@@ -135,7 +133,9 @@ pub async fn get_public_rooms_filtered_helper(
filter: &IncomingFilter,
_network: &IncomingRoomNetwork,
) -> ConduitResult<get_public_rooms_filtered::Response> {
if let Some(other_server) = server.filter(|server| *server != db.globals.server_name().as_str())
if let Some(other_server) = server
.clone()
.filter(|server| *server != db.globals.server_name().as_str())
{
let response = db
.sending
@@ -160,12 +160,15 @@ pub async fn get_public_rooms_filtered_helper(
.map(|c| {
// Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
// to ruma::api::client::r0::directory::PublicRoomsChunk
serde_json::from_str(
&serde_json::to_string(&c)
.expect("PublicRoomsChunk::to_string always works"),
Ok::<_, Error>(
serde_json::from_str(
&serde_json::to_string(&c)
.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(),
prev_batch: response.prev_batch,
next_batch: response.next_batch,
@@ -214,7 +217,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(None), |s| {
Ok(serde_json::from_value::<
Raw<canonical_alias::CanonicalAliasEventContent>,
>(s.content.clone())
>(s.content)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
.map_err(|_| {
@@ -227,7 +230,7 @@ pub async fn get_public_rooms_filtered_helper(
.room_state_get(&room_id, &EventType::RoomName, "")?
.map_or(Ok::<_, Error>(None), |s| {
Ok(serde_json::from_value::<Raw<name::NameEventContent>>(
s.content.clone(),
s.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -244,7 +247,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(None), |s| {
Ok(Some(
serde_json::from_value::<Raw<topic::TopicEventContent>>(
s.content.clone(),
s.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -260,7 +263,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(false), |s| {
Ok(serde_json::from_value::<
Raw<history_visibility::HistoryVisibilityEventContent>,
>(s.content.clone())
>(s.content)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
.map_err(|_| {
@@ -277,7 +280,7 @@ pub async fn get_public_rooms_filtered_helper(
.map_or(Ok::<_, Error>(false), |s| {
Ok(
serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>(
s.content.clone(),
s.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -294,7 +297,7 @@ pub async fn get_public_rooms_filtered_helper(
.map(|s| {
Ok::<_, Error>(
serde_json::from_value::<Raw<avatar::AvatarEventContent>>(
s.content.clone(),
s.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -312,36 +315,6 @@ pub async fn get_public_rooms_filtered_helper(
Ok(chunk)
})
.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
.collect::<Vec<_>>();
+96 -131
View File
@@ -1,5 +1,5 @@
use super::{State, SESSION_ID_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Result, Ruma};
use crate::{utils, ConduitResult, Database, Error, Ruma};
use ruma::{
api::client::{
error::ErrorKind,
@@ -12,12 +12,8 @@ use ruma::{
},
},
encryption::UnsignedDeviceInfo,
DeviceId, DeviceKeyAlgorithm, UserId,
};
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
};
use std::collections::{BTreeMap, HashSet};
#[cfg(feature = "conduit_bin")]
use rocket::{get, post};
@@ -28,7 +24,7 @@ use rocket::{get, post};
)]
#[tracing::instrument(skip(db, body))]
pub async fn upload_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<upload_keys::Request>,
) -> ConduitResult<upload_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -77,19 +73,79 @@ pub async fn upload_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_keys::Request<'_>>,
) -> ConduitResult<get_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let response = get_keys_helper(
Some(sender_user),
&body.device_keys,
|u| u == sender_user,
&db,
)?;
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();
Ok(response.into())
for (user_id, device_ids) in &body.device_keys {
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(
@@ -98,14 +154,32 @@ pub async fn get_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn claim_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<claim_keys::Request>,
) -> ConduitResult<claim_keys::Response> {
let response = claim_keys_helper(&body.one_time_keys, &db)?;
let mut one_time_keys = BTreeMap::new();
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?;
Ok(response.into())
Ok(claim_keys::Response {
failures: BTreeMap::new(),
one_time_keys,
}
.into())
}
#[cfg_attr(
@@ -114,7 +188,7 @@ pub async fn claim_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn upload_signing_keys_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<upload_signing_keys::Request<'_>>,
) -> ConduitResult<upload_signing_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -146,12 +220,7 @@ pub async fn upload_signing_keys_route(
// Success!
} else {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
db.uiaa.create(
&sender_user,
&sender_device,
&uiaainfo,
&body.json_body.expect("body is json"),
)?;
db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?;
return Err(Error::Uiaa(uiaainfo));
}
@@ -177,7 +246,7 @@ pub async fn upload_signing_keys_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn upload_signatures_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<upload_signatures::Request>,
) -> ConduitResult<upload_signatures::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -238,7 +307,7 @@ pub async fn upload_signatures_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_key_changes_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_key_changes::Request<'_>>,
) -> ConduitResult<get_key_changes::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -282,107 +351,3 @@ pub async fn get_key_changes_route(
}
.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,
})
}
+37 -56
View File
@@ -7,14 +7,14 @@ use ruma::api::client::{
#[cfg(feature = "conduit_bin")]
use rocket::{get, post};
use std::{convert::TryInto, sync::Arc};
use std::convert::TryInto;
const MXC_LENGTH: usize = 32;
#[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))]
#[tracing::instrument(skip(db))]
pub async fn get_media_config_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
) -> ConduitResult<get_media_config::Response> {
Ok(get_media_config::Response {
upload_size: db.globals.max_request_size().into(),
@@ -28,7 +28,7 @@ pub async fn get_media_config_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn create_content_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_content::Request<'_>>,
) -> ConduitResult<create_content::Response> {
let mxc = format!(
@@ -36,20 +36,12 @@ pub async fn create_content_route(
db.globals.server_name(),
utils::random_string(MXC_LENGTH)
);
db.media
.create(
mxc.clone(),
&db.globals,
&body
.filename
.as_ref()
.map(|filename| "inline; filename=".to_owned() + filename)
.as_deref(),
&body.content_type.as_deref(),
&body.file,
)
.await?;
db.media.create(
mxc.clone(),
&body.filename.as_deref(),
&body.content_type.as_deref(),
&body.file,
)?;
db.flush().await?;
@@ -66,21 +58,21 @@ pub async fn create_content_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_content_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_content::Request<'_>>,
) -> ConduitResult<get_content::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content_disposition,
filename,
content_type,
file,
}) = db.media.get(&db.globals, &mxc).await?
}) = db.media.get(&mxc)?
{
Ok(get_content::Response {
file,
content_type,
content_disposition,
content_disposition: filename,
}
.into())
} else if &*body.server_name != db.globals.server_name() && body.allow_remote {
@@ -97,15 +89,12 @@ pub async fn get_content_route(
)
.await?;
db.media
.create(
mxc,
&db.globals,
&get_content_response.content_disposition.as_deref(),
&get_content_response.content_type.as_deref(),
&get_content_response.file,
)
.await?;
db.media.create(
mxc,
&get_content_response.content_disposition.as_deref(),
&get_content_response.content_type.as_deref(),
&get_content_response.file,
)?;
Ok(get_content_response.into())
} else {
@@ -119,27 +108,22 @@ pub async fn get_content_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_content_thumbnail_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_content_thumbnail::Request<'_>>,
) -> ConduitResult<get_content_thumbnail::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content_type, file, ..
}) = db
.media
.get_thumbnail(
mxc.clone(),
&db.globals,
body.width
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
body.height
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
)
.await?
{
}) = db.media.get_thumbnail(
mxc.clone(),
body.width
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
body.height
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
)? {
Ok(get_content_thumbnail::Response { file, content_type }.into())
} else if &*body.server_name != db.globals.server_name() && body.allow_remote {
let get_thumbnail_response = db
@@ -158,17 +142,14 @@ pub async fn get_content_thumbnail_route(
)
.await?;
db.media
.upload_thumbnail(
mxc,
&db.globals,
&None,
&get_thumbnail_response.content_type,
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?;
db.media.upload_thumbnail(
mxc,
&None,
&get_thumbnail_response.content_type,
body.width.try_into().expect("all UInts are valid u32s"),
body.height.try_into().expect("all UInts are valid u32s"),
&get_thumbnail_response.file,
)?;
Ok(get_thumbnail_response.into())
} else {
+83 -82
View File
@@ -4,7 +4,7 @@ use crate::{
pdu::{PduBuilder, PduEvent},
server_server, utils, ConduitResult, Database, Error, Result, Ruma,
};
use log::{debug, error, warn};
use log::{error, warn};
use member::{MemberEventContent, MembershipState};
use rocket::futures;
use ruma::{
@@ -25,14 +25,13 @@ use ruma::{
EventType,
},
serde::{to_canonical_value, CanonicalJsonObject, CanonicalJsonValue, Raw},
state_res::{self, EventMap, RoomVersion},
uint, EventId, RoomId, RoomVersionId, ServerName, UserId,
};
use state_res::EventMap;
use std::{
collections::{btree_map::Entry, BTreeMap, HashSet},
collections::{BTreeMap, HashSet},
convert::{TryFrom, TryInto},
sync::{Arc, RwLock},
time::{Duration, Instant},
};
#[cfg(feature = "conduit_bin")]
@@ -44,7 +43,7 @@ use rocket::{get, post};
)]
#[tracing::instrument(skip(db, body))]
pub async fn join_room_by_id_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<join_room_by_id::Request<'_>>,
) -> ConduitResult<join_room_by_id::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -81,7 +80,7 @@ pub async fn join_room_by_id_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn join_room_by_id_or_alias_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<join_room_by_id_or_alias::Request<'_>>,
) -> ConduitResult<join_room_by_id_or_alias::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -135,7 +134,7 @@ pub async fn join_room_by_id_or_alias_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn leave_room_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<leave_room::Request<'_>>,
) -> ConduitResult<leave_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -153,7 +152,7 @@ pub async fn leave_room_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn invite_user_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<invite_user::Request<'_>>,
) -> ConduitResult<invite_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -173,7 +172,7 @@ pub async fn invite_user_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn kick_user_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<kick_user::Request<'_>>,
) -> ConduitResult<kick_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -189,8 +188,7 @@ pub async fn kick_user_route(
ErrorKind::BadState,
"Cannot kick member that's not in the room.",
))?
.content
.clone(),
.content,
)
.expect("Raw::from_value always works")
.deserialize()
@@ -223,7 +221,7 @@ pub async fn kick_user_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn ban_user_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<ban_user::Request<'_>>,
) -> ConduitResult<ban_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -246,12 +244,11 @@ pub async fn ban_user_route(
third_party_invite: None,
}),
|event| {
let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
event.content.clone(),
)
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
let mut event =
serde_json::from_value::<Raw<member::MemberEventContent>>(event.content)
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
event.membership = ruma::events::room::member::MembershipState::Ban;
Ok(event)
},
@@ -281,7 +278,7 @@ pub async fn ban_user_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn unban_user_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<unban_user::Request<'_>>,
) -> ConduitResult<unban_user::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -297,8 +294,7 @@ pub async fn unban_user_route(
ErrorKind::BadState,
"Cannot unban a user who is not banned.",
))?
.content
.clone(),
.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -330,7 +326,7 @@ pub async fn unban_user_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn forget_room_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<forget_room::Request<'_>>,
) -> ConduitResult<forget_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -348,7 +344,7 @@ pub async fn forget_room_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn joined_rooms_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<joined_rooms::Request>,
) -> ConduitResult<joined_rooms::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -369,7 +365,7 @@ pub async fn joined_rooms_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_member_events_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_member_events::Request<'_>>,
) -> ConduitResult<get_member_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -399,7 +395,7 @@ pub async fn get_member_events_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn joined_members_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<joined_members::Request<'_>>,
) -> ConduitResult<joined_members::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -549,6 +545,12 @@ async fn join_room_by_id_helper(
)
.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())
.map_err(|_| Error::BadServerResponse("Invalid join event PDU."))?;
@@ -576,6 +578,36 @@ async fn join_room_by_id_helper(
db.rooms.add_pdu_outlier(&event_id, &value)?;
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());
}
}
@@ -615,16 +647,11 @@ async fn join_room_by_id_helper(
// pdu without it's state. This is okay because append_pdu can't fail.
let statehashid = db.rooms.append_to_state(&pdu, &db.globals)?;
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());
db.rooms.append_pdu(
&pdu,
utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"),
count,
&pdu_id,
db.globals.next_count()?,
pdu_id.into(),
&[pdu.event_id.clone()],
db,
)?;
@@ -667,41 +694,9 @@ async fn validate_and_add_event_id(
db: &Database,
) -> Result<(EventId, CanonicalJsonObject)> {
let mut value = serde_json::from_str::<CanonicalJsonObject>(pdu.json().get()).map_err(|e| {
error!("Invalid PDU in server response: {:?}: {:?}", pdu, e);
error!("{:?}: {:?}", pdu, e);
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?;
if let Err(e) = ruma::signatures::verify_event(
@@ -711,11 +706,17 @@ async fn validate_and_add_event_id(
&value,
room_version,
) {
warn!("Event {} failed verification {:?} {}", event_id, pdu, e);
back_off(event_id);
warn!("Event failed verification: {}", e);
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(
"event_id".to_owned(),
CanonicalJsonValue::String(event_id.as_str().to_owned()),
@@ -746,27 +747,27 @@ pub async fn invite_helper(
let create_event_content = create_event
.as_ref()
.map(|create_event| {
serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone())
.expect("Raw::from_value always works.")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))
Ok::<_, Error>(
serde_json::from_value::<Raw<CreateEventContent>>(create_event.content.clone())
.expect("Raw::from_value always works.")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PowerLevels event in db."))?,
)
})
.transpose()?;
let create_prev_event = if prev_events.len() == 1
&& Some(&prev_events[0]) == create_event.as_ref().map(|c| &c.event_id)
{
create_event
create_event.map(Arc::new)
} else {
None
};
// If there was no create event yet, assume we are creating a version 6 room right now
let room_version_id = create_event_content
.map_or(RoomVersionId::Version6, |create_event| {
create_event.room_version
});
let room_version = RoomVersion::new(&room_version_id).expect("room version is supported");
let room_version = create_event_content.map_or(RoomVersionId::Version6, |create_event| {
create_event.room_version
});
let content = serde_json::to_value(MemberEventContent {
avatar_url: None,
@@ -795,10 +796,10 @@ pub async fn invite_helper(
let mut unsigned = BTreeMap::new();
if let Some(prev_pdu) = db.rooms.room_state_get(room_id, &kind, &state_key)? {
unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
unsigned.insert("prev_content".to_owned(), prev_pdu.content);
unsigned.insert(
"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"),
);
}
@@ -840,7 +841,7 @@ pub async fn invite_helper(
if !auth_check {
return Err(Error::BadRequest(
ErrorKind::Forbidden,
ErrorKind::InvalidParam,
"Event is not authorized.",
));
}
@@ -862,7 +863,7 @@ pub async fn invite_helper(
db.globals.server_name().as_str(),
db.globals.keypair(),
&mut pdu_json,
&room_version_id,
&room_version,
)
.expect("event is valid, we just created it");
@@ -946,7 +947,7 @@ pub async fn invite_helper(
membership: member::MembershipState::Invite,
displayname: db.users.displayname(&user_id)?,
avatar_url: db.users.avatar_url(&user_id)?,
is_direct: Some(is_direct),
is_direct: None,
third_party_invite: None,
})
.expect("event is valid, we just created it"),
+9 -5
View File
@@ -11,7 +11,6 @@ use ruma::{
use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
sync::Arc,
};
#[cfg(feature = "conduit_bin")]
@@ -23,7 +22,7 @@ use rocket::{get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn send_message_event_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<send_message_event::Request<'_>>,
) -> ConduitResult<send_message_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -57,8 +56,13 @@ pub async fn send_message_event_route(
let event_id = db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::from(&body.event_type),
content: serde_json::from_str(body.body.body.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
content: serde_json::from_str(
body.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),
state_key: None,
redacts: None,
@@ -86,7 +90,7 @@ pub async fn send_message_event_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_message_events_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_message_events::Request<'_>>,
) -> ConduitResult<get_message_events::Response> {
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,
};
pub const DEVICE_ID_LENGTH: usize = 10;
pub const TOKEN_LENGTH: usize = 256;
pub const SESSION_ID_LENGTH: usize = 256;
const DEVICE_ID_LENGTH: usize = 10;
const TOKEN_LENGTH: usize = 256;
const SESSION_ID_LENGTH: usize = 256;
#[cfg(feature = "conduit_bin")]
#[options("/<_..>")]
+4 -49
View File
@@ -1,10 +1,10 @@
use super::State;
use crate::{utils, ConduitResult, Database, Ruma};
use ruma::api::client::r0::presence::{get_presence, set_presence};
use std::{convert::TryInto, sync::Arc, time::Duration};
use ruma::api::client::r0::presence::set_presence;
use std::convert::TryInto;
#[cfg(feature = "conduit_bin")]
use rocket::{get, put};
use rocket::put;
#[cfg_attr(
feature = "conduit_bin",
@@ -12,7 +12,7 @@ use rocket::{get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_presence_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_presence::Request<'_>>,
) -> ConduitResult<set_presence::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -46,48 +46,3 @@ pub async fn set_presence_route(
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!();
}
}
+8 -10
View File
@@ -13,7 +13,7 @@ use ruma::{
#[cfg(feature = "conduit_bin")]
use rocket::{get, put};
use std::{convert::TryInto, sync::Arc};
use std::convert::TryInto;
#[cfg_attr(
feature = "conduit_bin",
@@ -21,7 +21,7 @@ use std::{convert::TryInto, sync::Arc};
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_displayname_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_display_name::Request<'_>>,
) -> ConduitResult<set_display_name::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -53,8 +53,7 @@ pub async fn set_displayname_route(
room.",
)
})?
.content
.clone(),
.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -108,7 +107,7 @@ pub async fn set_displayname_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_displayname_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_display_name::Request<'_>>,
) -> ConduitResult<get_display_name::Response> {
Ok(get_display_name::Response {
@@ -123,7 +122,7 @@ pub async fn get_displayname_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_avatar_url_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_avatar_url::Request<'_>>,
) -> ConduitResult<set_avatar_url::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -155,8 +154,7 @@ pub async fn set_avatar_url_route(
room.",
)
})?
.content
.clone(),
.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
@@ -210,7 +208,7 @@ pub async fn set_avatar_url_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_avatar_url_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_avatar_url::Request<'_>>,
) -> ConduitResult<get_avatar_url::Response> {
Ok(get_avatar_url::Response {
@@ -225,7 +223,7 @@ pub async fn get_avatar_url_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_profile_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_profile::Request<'_>>,
) -> ConduitResult<get_profile::Response> {
if !db.users.exists(&body.user_id)? {
+10 -12
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{
@@ -24,7 +22,7 @@ use rocket::{delete, get, post, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushrules_all_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_pushrules_all::Request>,
) -> ConduitResult<get_pushrules_all::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -49,7 +47,7 @@ pub async fn get_pushrules_all_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_pushrule::Request<'_>>,
) -> ConduitResult<get_pushrule::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -103,7 +101,7 @@ pub async fn get_pushrule_route(
)]
#[tracing::instrument(skip(db, req))]
pub async fn set_pushrule_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
req: Ruma<set_pushrule::Request<'_>>,
) -> ConduitResult<set_pushrule::Response> {
let sender_user = req.sender_user.as_ref().expect("user is authenticated");
@@ -206,7 +204,7 @@ pub async fn set_pushrule_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_actions_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_pushrule_actions::Request<'_>>,
) -> ConduitResult<get_pushrule_actions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -265,7 +263,7 @@ pub async fn get_pushrule_actions_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_pushrule_actions_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_pushrule_actions::Request<'_>>,
) -> ConduitResult<set_pushrule_actions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -339,7 +337,7 @@ pub async fn set_pushrule_actions_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushrule_enabled_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_pushrule_enabled::Request<'_>>,
) -> ConduitResult<get_pushrule_enabled::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -400,7 +398,7 @@ pub async fn get_pushrule_enabled_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_pushrule_enabled_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_pushrule_enabled::Request<'_>>,
) -> ConduitResult<set_pushrule_enabled::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -479,7 +477,7 @@ pub async fn set_pushrule_enabled_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_pushrule_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_pushrule::Request<'_>>,
) -> ConduitResult<delete_pushrule::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -548,7 +546,7 @@ pub async fn delete_pushrule_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushers_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_pushers::Request>,
) -> ConduitResult<get_pushers::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -565,7 +563,7 @@ pub async fn get_pushers_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_pushers_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_pusher::Request>,
) -> ConduitResult<set_pusher::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+31 -25
View File
@@ -5,14 +5,12 @@ use ruma::{
error::ErrorKind,
r0::{read_marker::set_read_marker, receipt::create_receipt},
},
events::{AnyEphemeralRoomEvent, EventType},
receipt::ReceiptType,
MilliSecondsSinceUnixEpoch,
events::{AnyEphemeralRoomEvent, AnyEvent, EventType},
};
#[cfg(feature = "conduit_bin")]
use rocket::post;
use std::{collections::BTreeMap, sync::Arc};
use std::{collections::BTreeMap, time::SystemTime};
#[cfg_attr(
feature = "conduit_bin",
@@ -20,7 +18,7 @@ use std::{collections::BTreeMap, sync::Arc};
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_read_marker_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<set_read_marker::Request<'_>>,
) -> ConduitResult<set_read_marker::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -29,6 +27,7 @@ pub async fn set_read_marker_route(
content: ruma::events::fully_read::FullyReadEventContent {
event_id: body.fully_read.clone(),
},
room_id: body.room_id.clone(),
};
db.account_data.update(
Some(&body.room_id),
@@ -55,23 +54,26 @@ pub async fn set_read_marker_route(
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
ts: Some(SystemTime::now()),
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(event.to_owned(), receipts);
receipt_content.insert(
event.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
db.rooms.edus.readreceipt_update(
&sender_user,
&body.room_id,
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
}),
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)),
&db.globals,
)?;
}
@@ -87,7 +89,7 @@ pub async fn set_read_marker_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn create_receipt_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_receipt::Request<'_>>,
) -> ConduitResult<create_receipt::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -110,22 +112,26 @@ pub async fn create_receipt_route(
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
ts: Some(SystemTime::now()),
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.to_owned(), receipts);
receipt_content.insert(
body.event_id.to_owned(),
ruma::events::receipt::Receipts {
read: Some(user_receipts),
},
);
db.rooms.edus.readreceipt_update(
&sender_user,
&body.room_id,
AnyEphemeralRoomEvent::Receipt(ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
}),
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)),
&db.globals,
)?;
+1 -2
View File
@@ -4,7 +4,6 @@ use ruma::{
api::client::r0::redact::redact_event,
events::{room::redaction, EventType},
};
use std::sync::Arc;
#[cfg(feature = "conduit_bin")]
use rocket::put;
@@ -15,7 +14,7 @@ use rocket::put;
)]
#[tracing::instrument(skip(db, body))]
pub async fn redact_event_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<redact_event::Request<'_>>,
) -> ConduitResult<redact_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+28 -28
View File
@@ -1,5 +1,6 @@
use super::State;
use crate::{client_server::invite_helper, pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
use crate::client_server::invite_helper;
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
use log::info;
use ruma::{
api::client::{
@@ -13,7 +14,7 @@ use ruma::{
serde::Raw,
RoomAliasId, RoomId, RoomVersionId,
};
use std::{cmp::max, collections::BTreeMap, convert::TryFrom, sync::Arc};
use std::{cmp::max, collections::BTreeMap, convert::TryFrom};
#[cfg(feature = "conduit_bin")]
use rocket::{get, post};
@@ -24,7 +25,7 @@ use rocket::{get, post};
)]
#[tracing::instrument(skip(db, body))]
pub async fn create_room_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_room::Request<'_>>,
) -> ConduitResult<create_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -111,26 +112,27 @@ pub async fn create_room_route(
}
}
let mut power_levels_content =
serde_json::to_value(ruma::events::room::power_levels::PowerLevelsEventContent {
users,
..Default::default()
})
.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(|_| {
let power_levels_content = if let Some(power_levels) = &body.power_level_content_override {
serde_json::from_str(power_levels.json().get()).map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
})?;
for (key, value) in json {
power_levels_content[key] = value;
}
}
})?
} else {
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_default: 0.into(),
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
room: 50.into(),
},
})
.expect("event is valid, we just created it")
};
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomPowerLevels,
@@ -294,7 +296,7 @@ pub async fn create_room_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_room_event_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_room_event::Request<'_>>,
) -> ConduitResult<get_room_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -322,7 +324,7 @@ pub async fn get_room_event_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn upgrade_room_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<upgrade_room::Request<'_>>,
_room_id: String,
) -> ConduitResult<upgrade_room::Response> {
@@ -362,8 +364,7 @@ pub async fn upgrade_room_route(
db.rooms
.room_state_get(&body.room_id, &EventType::RoomCreate, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.content
.clone(),
.content,
)
.expect("Raw::from_value always works")
.deserialize()
@@ -464,8 +465,7 @@ pub async fn upgrade_room_route(
db.rooms
.room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.content
.clone(),
.content,
)
.expect("database contains invalid PDU")
.deserialize()
+14 -42
View File
@@ -1,7 +1,6 @@
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::api::client::{error::ErrorKind, r0::search::search_events};
use std::sync::Arc;
#[cfg(feature = "conduit_bin")]
use rocket::post;
@@ -14,38 +13,23 @@ use std::collections::BTreeMap;
)]
#[tracing::instrument(skip(db, body))]
pub async fn search_events_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<search_events::Request<'_>>,
) -> ConduitResult<search_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let search_criteria = body.search_categories.room_events.as_ref().unwrap();
let filter = search_criteria.filter.clone().unwrap_or_default();
let filter = search_criteria.filter.as_ref().unwrap();
let room_ids = filter.rooms.clone().unwrap_or_else(|| {
db.rooms
.rooms_joined(&sender_user)
.filter_map(|r| r.ok())
.collect()
});
let room_id = filter.rooms.as_ref().unwrap().first().unwrap();
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
let mut searches = Vec::new();
for room_id in room_ids {
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());
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 skip = match body.next_batch.as_ref().map(|s| s.parse()) {
@@ -59,20 +43,12 @@ pub async fn search_events_route(
None => 0, // Default to the start
};
let mut results = Vec::new();
for _ in 0..skip + limit {
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 search = db
.rooms
.search_pdus(&room_id, &search_criteria.search_term)?;
let results = results
.iter()
let results = search
.0
.map(|result| {
Ok::<_, Error>(SearchResult {
context: EventContextResult {
@@ -107,11 +83,7 @@ pub async fn search_events_route(
next_batch,
results,
state: BTreeMap::new(), // TODO
highlights: search_criteria
.search_term
.split_terminator(|c: char| !c.is_alphanumeric())
.map(str::to_lowercase)
.collect::<Vec<_>>(),
highlights: search.1,
},
})
.into())
+7 -7
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Ruma};
use log::info;
@@ -52,7 +50,7 @@ pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response>
)]
#[tracing::instrument(skip(db, body))]
pub async fn login_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<login::Request<'_>>,
) -> ConduitResult<login::Response> {
// Validate login method
@@ -169,7 +167,7 @@ pub async fn login_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn logout_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<logout::Request>,
) -> ConduitResult<logout::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -197,13 +195,15 @@ pub async fn logout_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn logout_all_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<logout_all::Request>,
) -> ConduitResult<logout_all::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for device_id in db.users.all_device_ids(sender_user).flatten() {
db.users.remove_device(&sender_user, &device_id)?;
for device_id in db.users.all_device_ids(sender_user) {
if let Ok(device_id) = device_id {
db.users.remove_device(&sender_user, &device_id)?;
}
}
db.flush().await?;
+10 -12
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma};
use ruma::{
@@ -27,7 +25,7 @@ use rocket::{get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_key_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -53,7 +51,7 @@ pub async fn send_state_event_for_key_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_empty_key_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -79,7 +77,7 @@ pub async fn send_state_event_for_empty_key_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_state_events_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_state_events::Request<'_>>,
) -> ConduitResult<get_state_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -92,7 +90,7 @@ pub async fn get_state_events_route(
db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| {
Error::bad_database(
"Invalid room history visibility event in database.",
@@ -126,7 +124,7 @@ pub async fn get_state_events_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_state_events_for_key_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_state_events_for_key::Request<'_>>,
) -> ConduitResult<get_state_events_for_key::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -139,7 +137,7 @@ pub async fn get_state_events_for_key_route(
db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| {
Error::bad_database(
"Invalid room history visibility event in database.",
@@ -165,7 +163,7 @@ pub async fn get_state_events_for_key_route(
))?;
Ok(get_state_events_for_key::Response {
content: serde_json::from_value(event.content.clone())
content: serde_json::from_value(event.content)
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
}
.into())
@@ -177,7 +175,7 @@ pub async fn get_state_events_for_key_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_state_events_for_empty_key_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_state_events_for_key::Request<'_>>,
) -> ConduitResult<get_state_events_for_key::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -190,7 +188,7 @@ pub async fn get_state_events_for_empty_key_route(
db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content.clone())
serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| {
Error::bad_database(
"Invalid room history visibility event in database.",
@@ -216,7 +214,7 @@ pub async fn get_state_events_for_empty_key_route(
))?;
Ok(get_state_events_for_key::Response {
content: serde_json::from_value(event.content.clone())
content: serde_json::from_value(event.content)
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
}
.into())
+54 -185
View File
@@ -1,22 +1,19 @@
use super::State;
use crate::{ConduitResult, Database, Error, Result, Ruma, RumaResponse};
use log::error;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{
api::client::r0::{sync::sync_events, uiaa::UiaaResponse},
api::client::r0::sync::sync_events,
events::{room::member::MembershipState, AnySyncEphemeralRoomEvent, EventType},
serde::Raw,
DeviceId, RoomId, UserId,
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")]
use rocket::{get, tokio};
use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet},
convert::{TryFrom, TryInto},
time::Duration,
};
/// # `GET /_matrix/client/r0/sync`
///
@@ -35,136 +32,23 @@ use rocket::{get, tokio};
)]
#[tracing::instrument(skip(db, body))]
pub async fn sync_events_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<sync_events::Request<'_>>,
) -> std::result::Result<RumaResponse<sync_events::Response>, RumaResponse<UiaaResponse>> {
) -> ConduitResult<sync_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
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 {
let _ = rx.changed().await;
}
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 {
db.rooms.edus.ping_presence(&sender_user)?;
// 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()?;
let next_batch_string = next_batch.to_string();
let next_batch = db.globals.current_count()?.to_string();
let mut joined_rooms = BTreeMap::new();
let since = since
let since = body
.since
.clone()
.and_then(|string| string.parse().ok())
.unwrap_or(0);
@@ -186,23 +70,13 @@ async fn sync_helper(
let mut non_timeline_pdus = db
.rooms
.pdus_until(&sender_user, &room_id, u64::MAX)
.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)
});
.pdus_since(&sender_user, &room_id, since)?
.filter_map(|r| r.ok()); // Filter out buggy events
// Take the last 10 events for the timeline
let timeline_pdus = non_timeline_pdus
.by_ref()
.rev()
.take(10)
.collect::<Vec<_>>()
.into_iter()
@@ -228,11 +102,10 @@ async fn sync_helper(
// 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 first_pdu_before_since = db.rooms.pdus_until(sender_user, &room_id, since).next();
let pdus_after_since = db
.rooms
.pdus_after(&sender_user, &room_id, since)
.pdus_after(sender_user, &room_id, since)
.next()
.is_some();
@@ -261,9 +134,11 @@ async fn sync_helper(
let since_state = since_shortstatehash
.as_ref()
.map(|since_shortstatehash| {
since_shortstatehash
.map(|since_shortstatehash| db.rooms.state_full(since_shortstatehash))
.transpose()
Ok::<_, Error>(
since_shortstatehash
.map(|since_shortstatehash| db.rooms.state_full(since_shortstatehash))
.transpose()?,
)
})
.transpose()?;
@@ -345,11 +220,10 @@ async fn sync_helper(
match (since_membership, current_membership) {
(MembershipState::Leave, MembershipState::Join) => {
// 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);
}
}
// TODO: Remove, this should never happen here, right?
(MembershipState::Join, MembershipState::Leave) => {
// Write down users that have left encrypted rooms we are in
left_encrypted_users.insert(user_id);
@@ -368,15 +242,14 @@ async fn sync_helper(
device_list_updates.extend(
db.rooms
.room_members(&room_id)
.flatten()
.filter_map(|user_id| Some(user_id.ok()?))
.filter(|user_id| {
// Don't send key updates from the sender to the sender
&sender_user != user_id
sender_user != user_id
})
.filter(|user_id| {
// 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)
.unwrap_or(false)
!share_encrypted_room(&db, sender_user, user_id, &room_id)
}),
);
}
@@ -394,13 +267,15 @@ async fn sync_helper(
for hero in db
.rooms
.all_pdus(&sender_user, &room_id)
.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,
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.")
})?;
@@ -426,10 +301,9 @@ async fn sync_helper(
Ok(None)
}
})
// Filter out buggy users
.filter_map(|u| u.ok())
.filter_map(|u| u.ok()) // Filter out buggy users
// Filter for possible heroes
.flatten()
.filter_map(|u| u)
{
if heroes.contains(&hero) || hero == sender_user.as_str() {
continue;
@@ -450,7 +324,7 @@ async fn sync_helper(
let state_events = if joined_since_last_sync {
current_state
.iter()
.into_iter()
.map(|(_, pdu)| pdu.to_sync_state_event())
.collect()
} else {
@@ -530,9 +404,8 @@ async fn sync_helper(
let mut edus = db
.rooms
.edus
.readreceipts_since(&room_id, since)
.readreceipts_since(&room_id, since)?
.filter_map(|r| r.ok()) // Filter out buggy events
.map(|(_, _, v)| v)
.collect::<Vec<_>>();
if db.rooms.edus.last_typing_update(&room_id, &db.globals)? > since {
@@ -548,7 +421,7 @@ async fn sync_helper(
}
let joined_room = sync_events::JoinedRoom {
account_data: sync_events::RoomAccountData {
account_data: sync_events::AccountData {
events: db
.account_data
.changes_since(Some(&room_id), &sender_user, since)?
@@ -632,10 +505,10 @@ async fn sync_helper(
left_rooms.insert(
room_id.clone(),
sync_events::LeftRoom {
account_data: sync_events::RoomAccountData { events: Vec::new() },
account_data: sync_events::AccountData { events: Vec::new() },
timeline: sync_events::Timeline {
limited: false,
prev_batch: Some(next_batch_string.clone()),
prev_batch: Some(next_batch.clone()),
events: Vec::new(),
},
state: sync_events::State {
@@ -668,7 +541,7 @@ async fn sync_helper(
for user_id in left_encrypted_users {
let still_share_encrypted_room = db
.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(|other_room_id| {
Some(
@@ -688,10 +561,10 @@ async fn sync_helper(
// Remove all to-device events the device received *last time*
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 {
next_batch: next_batch_string,
next_batch,
rooms: sync_events::Rooms {
leave: left_rooms,
join: joined_rooms,
@@ -703,7 +576,7 @@ async fn sync_helper(
.map(|(_, v)| Raw::from(v))
.collect(),
},
account_data: sync_events::GlobalAccountData {
account_data: sync_events::AccountData {
events: db
.account_data
.changes_since(None, &sender_user, since)?
@@ -719,22 +592,20 @@ async fn sync_helper(
changed: device_list_updates.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
{
db.users.count_one_time_keys(&sender_user, &sender_device)?
db.users.count_one_time_keys(sender_user, sender_device)?
} else {
BTreeMap::new()
},
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)
if !full_state
if !body.full_state
&& response.rooms.is_empty()
&& response.presence.is_empty()
&& response.account_data.is_empty()
@@ -744,15 +615,14 @@ async fn sync_helper(
{
// Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives
let mut duration = timeout.unwrap_or_default();
let mut duration = body.timeout.unwrap_or_default();
if duration.as_secs() > 30 {
duration = Duration::from_secs(30);
}
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))]
@@ -761,10 +631,9 @@ fn share_encrypted_room(
sender_user: &UserId,
user_id: &UserId,
ignore_room: &RoomId,
) -> Result<bool> {
Ok(db
.rooms
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
) -> bool {
db.rooms
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])
.filter_map(|r| r.ok())
.filter(|room_id| room_id != ignore_room)
.filter_map(|other_room_id| {
@@ -775,5 +644,5 @@ fn share_encrypted_room(
.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},
events::EventType,
};
use std::{collections::BTreeMap, sync::Arc};
use std::collections::BTreeMap;
#[cfg(feature = "conduit_bin")]
use rocket::{delete, get, put};
@@ -15,7 +15,7 @@ use rocket::{delete, get, put};
)]
#[tracing::instrument(skip(db, body))]
pub async fn update_tag_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_tag::Request<'_>>,
) -> ConduitResult<create_tag::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -31,7 +31,7 @@ pub async fn update_tag_route(
tags_event
.content
.tags
.insert(body.tag.clone().into(), body.tag_info.clone());
.insert(body.tag.to_string(), body.tag_info.clone());
db.account_data.update(
Some(&body.room_id),
@@ -52,7 +52,7 @@ pub async fn update_tag_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn delete_tag_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<delete_tag::Request<'_>>,
) -> ConduitResult<delete_tag::Response> {
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_event.content.tags.remove(&body.tag.clone().into());
tags_event.content.tags.remove(&body.tag);
db.account_data.update(
Some(&body.room_id),
@@ -86,7 +86,7 @@ pub async fn delete_tag_route(
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_tags_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<get_tags::Request<'_>>,
) -> ConduitResult<get_tags::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+18 -18
View File
@@ -1,10 +1,8 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{
api::client::{error::ErrorKind, r0::to_device::send_event_to_device},
to_device::DeviceIdOrAllDevices,
use ruma::api::client::{
error::ErrorKind,
r0::to_device::{self, send_event_to_device},
};
#[cfg(feature = "conduit_bin")]
@@ -16,7 +14,7 @@ use rocket::put;
)]
#[tracing::instrument(skip(db, body))]
pub async fn send_event_to_device_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<send_event_to_device::Request<'_>>,
) -> ConduitResult<send_event_to_device::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -34,25 +32,27 @@ pub async fn send_event_to_device_route(
for (target_user_id, map) in &body.messages {
for (target_device_id_maybe, event) in map {
match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => db.users.add_to_device_event(
sender_user,
&target_user_id,
&target_device_id,
&body.event_type,
event.deserialize_as().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
})?,
&db.globals,
)?,
to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => {
db.users.add_to_device_event(
sender_user,
&target_user_id,
&target_device_id,
&body.event_type,
serde_json::from_str(event.get()).map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
})?,
&db.globals,
)?
}
DeviceIdOrAllDevices::AllDevices => {
to_device::DeviceIdOrAllDevices::AllDevices => {
for target_device_id in db.users.all_device_ids(&target_user_id) {
db.users.add_to_device_event(
sender_user,
&target_user_id,
&target_device_id?,
&body.event_type,
event.deserialize_as().map_err(|_| {
serde_json::from_str(event.get()).map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
})?,
&db.globals,
+1 -3
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{utils, ConduitResult, Database, Ruma};
use create_typing_event::Typing;
@@ -14,7 +12,7 @@ use rocket::put;
)]
#[tracing::instrument(skip(db, body))]
pub fn create_typing_event_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<create_typing_event::Request<'_>>,
) -> ConduitResult<create_typing_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+6 -12
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::State;
use crate::{ConduitResult, Database, Ruma};
use ruma::api::client::r0::user_directory::search_users;
@@ -13,7 +11,7 @@ use rocket::post;
)]
#[tracing::instrument(skip(db, body))]
pub async fn search_users_route(
db: State<'_, Arc<Database>>,
db: State<'_, Database>,
body: Ruma<search_users::Request<'_>>,
) -> ConduitResult<search_users::Response> {
let limit = u64::from(body.limit) as usize;
@@ -21,6 +19,9 @@ pub async fn search_users_route(
let mut users = db.users.iter().filter_map(|user_id| {
// Filter out buggy users (they should not exist, but you never know...)
let user_id = user_id.ok()?;
if db.users.is_deactivated(&user_id).ok()? {
return None;
}
let user = search_users::User {
user_id: user_id.clone(),
@@ -28,18 +29,11 @@ pub async fn search_users_route(
avatar_url: db.users.avatar_url(&user_id).ok()?,
};
if !user
.user_id
.to_string()
.to_lowercase()
.contains(&body.search_term.to_lowercase())
if !user.user_id.to_string().contains(&body.search_term)
&& user
.display_name
.as_ref()
.filter(|name| {
name.to_lowercase()
.contains(&body.search_term.to_lowercase())
})
.filter(|name| name.contains(&body.search_term))
.is_none()
{
return None;
+81 -184
View File
@@ -1,5 +1,3 @@
pub mod abstraction;
pub mod account_data;
pub mod admin;
pub mod appservice;
@@ -13,18 +11,16 @@ pub mod transaction_ids;
pub mod uiaa;
pub mod users;
use crate::{utils, Error, Result};
use abstraction::DatabaseEngine;
use crate::{Error, Result};
use directories::ProjectDirs;
use log::error;
use lru_cache::LruCache;
use rocket::futures::{channel::mpsc, stream::FuturesUnordered, StreamExt};
use futures::StreamExt;
use log::info;
use rocket::futures::{self, channel::mpsc};
use ruma::{DeviceId, ServerName, UserId};
use serde::Deserialize;
use std::{
collections::HashMap,
fs::{self, remove_dir_all},
io::Write,
fs::remove_dir_all,
sync::{Arc, RwLock},
};
use tokio::sync::Semaphore;
@@ -71,19 +67,14 @@ fn default_max_request_size() -> u32 {
}
fn default_max_concurrent_requests() -> u16 {
100
4
}
fn default_log() -> String {
"info,state_res=warn,rocket=off,_=off,sled=off".to_owned()
}
#[cfg(feature = "sled")]
pub type Engine = abstraction::SledEngine;
#[cfg(feature = "rocksdb")]
pub type Engine = abstraction::RocksDbEngine;
#[derive(Clone)]
pub struct Database {
pub globals: globals::Globals,
pub users: users::Users,
@@ -97,6 +88,7 @@ pub struct Database {
pub admin: admin::Admin,
pub appservice: appservice::Appservice,
pub pusher: pusher::PushData,
pub _db: sled::Db,
}
impl Database {
@@ -113,213 +105,118 @@ impl Database {
}
/// Load an existing database or create a new one.
pub async fn load_or_create(config: Config) -> Result<Arc<Self>> {
let builder = Engine::open(&config)?;
pub async fn load_or_create(config: Config) -> Result<Self> {
let db = sled::Config::default()
.path(&config.database_path)
.cache_capacity(config.cache_capacity as u64)
.use_compression(true)
.open()?;
if config.max_request_size < 1024 {
eprintln!("ERROR: Max request size is less than 1KB. Please increase it.");
}
info!("Opened sled database at {}", config.database_path);
let (admin_sender, admin_receiver) = mpsc::unbounded();
let (sending_sender, sending_receiver) = mpsc::unbounded();
let db = Arc::new(Self {
let db = Self {
users: users::Users {
userid_password: builder.open_tree("userid_password")?,
userid_displayname: builder.open_tree("userid_displayname")?,
userid_avatarurl: builder.open_tree("userid_avatarurl")?,
userdeviceid_token: builder.open_tree("userdeviceid_token")?,
userdeviceid_metadata: builder.open_tree("userdeviceid_metadata")?,
userid_devicelistversion: builder.open_tree("userid_devicelistversion")?,
token_userdeviceid: builder.open_tree("token_userdeviceid")?,
onetimekeyid_onetimekeys: builder.open_tree("onetimekeyid_onetimekeys")?,
userid_lastonetimekeyupdate: builder.open_tree("userid_lastonetimekeyupdate")?,
keychangeid_userid: builder.open_tree("keychangeid_userid")?,
keyid_key: builder.open_tree("keyid_key")?,
userid_masterkeyid: builder.open_tree("userid_masterkeyid")?,
userid_selfsigningkeyid: builder.open_tree("userid_selfsigningkeyid")?,
userid_usersigningkeyid: builder.open_tree("userid_usersigningkeyid")?,
todeviceid_events: builder.open_tree("todeviceid_events")?,
userid_password: db.open_tree("userid_password")?,
userid_displayname: db.open_tree("userid_displayname")?,
userid_avatarurl: db.open_tree("userid_avatarurl")?,
userdeviceid_token: db.open_tree("userdeviceid_token")?,
userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?,
userid_devicelistversion: db.open_tree("userid_devicelistversion")?,
token_userdeviceid: db.open_tree("token_userdeviceid")?,
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?,
userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?,
keychangeid_userid: db.open_tree("keychangeid_userid")?,
keyid_key: db.open_tree("keyid_key")?,
userid_masterkeyid: db.open_tree("userid_masterkeyid")?,
userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?,
userid_usersigningkeyid: db.open_tree("userid_usersigningkeyid")?,
todeviceid_events: db.open_tree("todeviceid_events")?,
},
uiaa: uiaa::Uiaa {
userdevicesessionid_uiaainfo: builder.open_tree("userdevicesessionid_uiaainfo")?,
userdevicesessionid_uiaarequest: builder
.open_tree("userdevicesessionid_uiaarequest")?,
userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo")?,
},
rooms: rooms::Rooms {
edus: rooms::RoomEdus {
readreceiptid_readreceipt: builder.open_tree("readreceiptid_readreceipt")?,
roomuserid_privateread: builder.open_tree("roomuserid_privateread")?, // "Private" read receipt
roomuserid_lastprivatereadupdate: builder
readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?,
roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt
roomuserid_lastprivatereadupdate: db
.open_tree("roomuserid_lastprivatereadupdate")?,
typingid_userid: builder.open_tree("typingid_userid")?,
roomid_lasttypingupdate: builder.open_tree("roomid_lasttypingupdate")?,
presenceid_presence: builder.open_tree("presenceid_presence")?,
userid_lastpresenceupdate: builder.open_tree("userid_lastpresenceupdate")?,
typingid_userid: db.open_tree("typingid_userid")?,
roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?,
presenceid_presence: db.open_tree("presenceid_presence")?,
userid_lastpresenceupdate: db.open_tree("userid_lastpresenceupdate")?,
},
pduid_pdu: builder.open_tree("pduid_pdu")?,
eventid_pduid: builder.open_tree("eventid_pduid")?,
roomid_pduleaves: builder.open_tree("roomid_pduleaves")?,
pduid_pdu: db.open_tree("pduid_pdu")?,
eventid_pduid: db.open_tree("eventid_pduid")?,
roomid_pduleaves: db.open_tree("roomid_pduleaves")?,
alias_roomid: builder.open_tree("alias_roomid")?,
aliasid_alias: builder.open_tree("aliasid_alias")?,
publicroomids: builder.open_tree("publicroomids")?,
alias_roomid: db.open_tree("alias_roomid")?,
aliasid_alias: db.open_tree("aliasid_alias")?,
publicroomids: db.open_tree("publicroomids")?,
tokenids: builder.open_tree("tokenids")?,
tokenids: db.open_tree("tokenids")?,
roomserverids: builder.open_tree("roomserverids")?,
serverroomids: builder.open_tree("serverroomids")?,
userroomid_joined: builder.open_tree("userroomid_joined")?,
roomuserid_joined: builder.open_tree("roomuserid_joined")?,
roomuseroncejoinedids: builder.open_tree("roomuseroncejoinedids")?,
userroomid_invitestate: builder.open_tree("userroomid_invitestate")?,
roomuserid_invitecount: builder.open_tree("roomuserid_invitecount")?,
userroomid_leftstate: builder.open_tree("userroomid_leftstate")?,
roomuserid_leftcount: builder.open_tree("roomuserid_leftcount")?,
roomserverids: db.open_tree("roomserverids")?,
userroomid_joined: db.open_tree("userroomid_joined")?,
roomuserid_joined: db.open_tree("roomuserid_joined")?,
roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?,
userroomid_invitestate: db.open_tree("userroomid_invitestate")?,
roomuserid_invitecount: db.open_tree("roomuserid_invitecount")?,
userroomid_leftstate: db.open_tree("userroomid_leftstate")?,
roomuserid_leftcount: db.open_tree("roomuserid_leftcount")?,
userroomid_notificationcount: builder.open_tree("userroomid_notificationcount")?,
userroomid_highlightcount: builder.open_tree("userroomid_highlightcount")?,
userroomid_notificationcount: db.open_tree("userroomid_notificationcount")?,
userroomid_highlightcount: db.open_tree("userroomid_highlightcount")?,
statekey_shortstatekey: builder.open_tree("statekey_shortstatekey")?,
stateid_shorteventid: builder.open_tree("stateid_shorteventid")?,
eventid_shorteventid: builder.open_tree("eventid_shorteventid")?,
shorteventid_eventid: builder.open_tree("shorteventid_eventid")?,
shorteventid_shortstatehash: builder.open_tree("shorteventid_shortstatehash")?,
roomid_shortstatehash: builder.open_tree("roomid_shortstatehash")?,
statehash_shortstatehash: builder.open_tree("statehash_shortstatehash")?,
statekey_shortstatekey: db.open_tree("statekey_shortstatekey")?,
stateid_shorteventid: db.open_tree("stateid_shorteventid")?,
eventid_shorteventid: db.open_tree("eventid_shorteventid")?,
shorteventid_eventid: db.open_tree("shorteventid_eventid")?,
shorteventid_shortstatehash: db.open_tree("shorteventid_shortstatehash")?,
roomid_shortstatehash: db.open_tree("roomid_shortstatehash")?,
statehash_shortstatehash: db.open_tree("statehash_shortstatehash")?,
eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
prevevent_parent: builder.open_tree("prevevent_parent")?,
pdu_cache: RwLock::new(LruCache::new(1_000_000)),
eventid_outlierpdu: db.open_tree("eventid_outlierpdu")?,
prevevent_parent: db.open_tree("prevevent_parent")?,
},
account_data: account_data::AccountData {
roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
},
media: media::Media {
mediaid_file: builder.open_tree("mediaid_file")?,
mediaid_file: db.open_tree("mediaid_file")?,
},
key_backups: key_backups::KeyBackups {
backupid_algorithm: builder.open_tree("backupid_algorithm")?,
backupid_etag: builder.open_tree("backupid_etag")?,
backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
backupid_algorithm: db.open_tree("backupid_algorithm")?,
backupid_etag: db.open_tree("backupid_etag")?,
backupkeyid_backup: db.open_tree("backupkeyid_backup")?,
},
transaction_ids: transaction_ids::TransactionIds {
userdevicetxnid_response: builder.open_tree("userdevicetxnid_response")?,
userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?,
},
sending: sending::Sending {
servername_educount: builder.open_tree("servername_educount")?,
servernamepduids: builder.open_tree("servernamepduids")?,
servercurrentevents: builder.open_tree("servercurrentevents")?,
servernamepduids: db.open_tree("servernamepduids")?,
servercurrentpdus: db.open_tree("servercurrentpdus")?,
maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests as usize)),
sender: sending_sender,
},
admin: admin::Admin {
sender: admin_sender,
},
appservice: appservice::Appservice {
cached_registrations: Arc::new(RwLock::new(HashMap::new())),
id_appserviceregistrations: builder.open_tree("id_appserviceregistrations")?,
},
pusher: pusher::PushData {
senderkey_pusher: builder.open_tree("senderkey_pusher")?,
id_appserviceregistrations: db.open_tree("id_appserviceregistrations")?,
},
pusher: pusher::PushData::new(&db)?,
globals: globals::Globals::load(
builder.open_tree("global")?,
builder.open_tree("server_signingkeys")?,
db.open_tree("global")?,
db.open_tree("servertimeout_signingkey")?,
config,
)?,
});
_db: db,
};
// 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);
db.admin.start_handler(db.clone(), admin_receiver);
Ok(db)
}
@@ -333,7 +230,7 @@ impl Database {
userdeviceid_prefix.extend_from_slice(device_id.as_bytes());
userdeviceid_prefix.push(0xff);
let mut futures = FuturesUnordered::new();
let mut futures = futures::stream::FuturesUnordered::new();
// Return when *any* user changed his key
// TODO: only send for user they share a room with
-329
View File
@@ -1,329 +0,0 @@
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(false)
.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();
})
}
}
+33 -30
View File
@@ -1,17 +1,17 @@
use crate::{utils, Error, Result};
use ruma::{
api::client::error::ErrorKind,
events::{AnyEphemeralRoomEvent, EventType},
events::{AnyEvent as EduEvent, EventType},
serde::Raw,
RoomId, UserId,
};
use serde::{de::DeserializeOwned, Serialize};
use std::{collections::HashMap, convert::TryFrom, sync::Arc};
use super::abstraction::Tree;
use sled::IVec;
use std::{collections::HashMap, convert::TryFrom};
#[derive(Clone)]
pub struct AccountData {
pub(super) roomuserdataid_accountdata: Arc<dyn Tree>, // RoomUserDataId = Room + User + Count + Type
pub(super) roomuserdataid_accountdata: sled::Tree, // RoomUserDataId = Room + User + Count + Type
}
impl AccountData {
@@ -34,8 +34,9 @@ impl AccountData {
prefix.push(0xff);
// Remove old entry
if let Some((old_key, _)) = self.find_event(room_id, user_id, &event_type)? {
self.roomuserdataid_accountdata.remove(&old_key)?;
if let Some(previous) = self.find_event(room_id, user_id, &event_type) {
let (old_key, _) = previous?;
self.roomuserdataid_accountdata.remove(old_key)?;
}
let mut key = prefix;
@@ -51,10 +52,8 @@ impl AccountData {
));
}
self.roomuserdataid_accountdata.insert(
&key,
&serde_json::to_vec(&json).expect("to_vec always works on json values"),
)?;
self.roomuserdataid_accountdata
.insert(key, &*json.to_string())?;
Ok(())
}
@@ -66,8 +65,9 @@ impl AccountData {
user_id: &UserId,
kind: EventType,
) -> Result<Option<T>> {
self.find_event(room_id, user_id, &kind)?
.map(|(_, v)| {
self.find_event(room_id, user_id, &kind)
.map(|r| {
let (_, v) = r?;
serde_json::from_slice(&v).map_err(|_| Error::bad_database("could not deserialize"))
})
.transpose()
@@ -80,7 +80,7 @@ impl AccountData {
room_id: Option<&RoomId>,
user_id: &UserId,
since: u64,
) -> Result<HashMap<EventType, Raw<AnyEphemeralRoomEvent>>> {
) -> Result<HashMap<EventType, Raw<EduEvent>>> {
let mut userdata = HashMap::new();
let mut prefix = room_id
@@ -98,7 +98,8 @@ impl AccountData {
for r in self
.roomuserdataid_accountdata
.iter_from(&first_possible, false)
.range(&*first_possible..)
.filter_map(|r| r.ok())
.take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(k, v)| {
Ok::<_, Error>((
@@ -109,7 +110,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."))?,
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
serde_json::from_slice::<Raw<EduEvent>>(&v).map_err(|_| {
Error::bad_database("Database contains invalid account data.")
})?,
))
@@ -127,7 +128,7 @@ impl AccountData {
room_id: Option<&RoomId>,
user_id: &UserId,
kind: &EventType,
) -> Result<Option<(Box<[u8]>, Box<[u8]>)>> {
) -> Option<Result<(IVec, IVec)>> {
let mut prefix = room_id
.map(|r| r.to_string())
.unwrap_or_default()
@@ -136,21 +137,23 @@ impl AccountData {
prefix.push(0xff);
prefix.extend_from_slice(&user_id.as_bytes());
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();
Ok(self
.roomuserdataid_accountdata
.iter_from(&last_possible_key, true)
.take_while(move |(k, _)| k.starts_with(&prefix))
.find(move |(k, _)| {
k.rsplit(|&b| b == 0xff)
.next()
.map(|current_event_type| current_event_type == kind.as_ref().as_bytes())
self.roomuserdataid_accountdata
.scan_prefix(prefix)
.rev()
.find(move |r| {
r.as_ref()
.map(|(k, _)| {
k.rsplit(|&b| b == 0xff)
.next()
.map(|current_event_type| {
current_event_type == kind.as_ref().as_bytes()
})
.unwrap_or(false)
})
.unwrap_or(false)
}))
})
.map(|r| Ok(r?))
}
}
+12 -18
View File
@@ -1,9 +1,6 @@
use std::{
convert::{TryFrom, TryInto},
sync::Arc,
};
use std::convert::{TryFrom, TryInto};
use crate::{pdu::PduBuilder, Database};
use crate::pdu::PduBuilder;
use log::warn;
use rocket::futures::{channel::mpsc, stream::StreamExt};
use ruma::{
@@ -25,7 +22,7 @@ pub struct Admin {
impl Admin {
pub fn start_handler(
&self,
db: Arc<Database>,
db: super::Database,
mut receiver: mpsc::UnboundedReceiver<AdminCommand>,
) {
tokio::spawn(async move {
@@ -76,17 +73,14 @@ impl Admin {
db.appservice.register_appservice(yaml).unwrap(); // TODO handle error
}
AdminCommand::ListAppservices => {
if let Ok(appservices) = db.appservice.iter_ids().map(|ids| ids.collect::<Vec<_>>()) {
let count = appservices.len();
let output = format!(
"Appservices ({}): {}",
count,
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
);
send_message(message::MessageEventContent::text_plain(output));
} else {
send_message(message::MessageEventContent::text_plain("Failed to get appservices."));
}
let appservices = db.appservice.iter_ids().collect::<Vec<_>>();
let count = appservices.len();
let output = format!(
"Appservices ({}): {}",
count,
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
);
send_message(message::MessageEventContent::text_plain(output));
}
AdminCommand::SendMessage(message) => {
send_message(message);
@@ -99,6 +93,6 @@ impl Admin {
}
pub fn send(&self, command: AdminCommand) {
self.sender.unbounded_send(command).unwrap();
self.sender.unbounded_send(command).unwrap()
}
}
+19 -22
View File
@@ -4,21 +4,18 @@ use std::{
sync::{Arc, RwLock},
};
use super::abstraction::Tree;
#[derive(Clone)]
pub struct Appservice {
pub(super) cached_registrations: Arc<RwLock<HashMap<String, serde_yaml::Value>>>,
pub(super) id_appserviceregistrations: Arc<dyn Tree>,
pub(super) id_appserviceregistrations: sled::Tree,
}
impl Appservice {
pub fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<()> {
// TODO: Rumaify
let id = yaml.get("id").unwrap().as_str().unwrap();
self.id_appserviceregistrations.insert(
id.as_bytes(),
serde_yaml::to_string(&yaml).unwrap().as_bytes(),
)?;
self.id_appserviceregistrations
.insert(id, serde_yaml::to_string(&yaml).unwrap().as_bytes())?;
self.cached_registrations
.write()
.unwrap()
@@ -34,37 +31,37 @@ impl Appservice {
.get(id)
.map_or_else(
|| {
self.id_appserviceregistrations
.get(id.as_bytes())?
Ok(self
.id_appserviceregistrations
.get(id)?
.map(|bytes| {
serde_yaml::from_slice(&bytes).map_err(|_| {
Ok::<_, Error>(serde_yaml::from_slice(&bytes).map_err(|_| {
Error::bad_database(
"Invalid registration bytes in id_appserviceregistrations.",
)
})
})?)
})
.transpose()
.transpose()?)
},
|r| Ok(Some(r.clone())),
)
}
pub fn iter_ids(&self) -> Result<impl Iterator<Item = Result<String>> + Send + Sync + '_> {
Ok(self.id_appserviceregistrations.iter().map(|(id, _)| {
utils::string_from_bytes(&id)
.map_err(|_| Error::bad_database("Invalid id bytes in id_appserviceregistrations."))
}))
pub fn iter_ids(&self) -> impl Iterator<Item = Result<String>> {
self.id_appserviceregistrations.iter().keys().map(|id| {
Ok(utils::string_from_bytes(&id?).map_err(|_| {
Error::bad_database("Invalid id bytes in id_appserviceregistrations.")
})?)
})
}
pub fn iter_all(
&self,
) -> Result<impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ + Send + Sync> {
Ok(self.iter_ids()?.filter_map(|id| id.ok()).map(move |id| {
pub fn iter_all(&self) -> impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ {
self.iter_ids().filter_map(|id| id.ok()).map(move |id| {
Ok((
id.clone(),
self.get_registration(&id)?
.expect("iter_ids only returns appservices that exist"),
))
}))
})
}
}
+66 -117
View File
@@ -1,52 +1,32 @@
use crate::{database::Config, utils, ConduitResult, Error, Result};
use crate::{database::Config, utils, Error, Result};
use log::{error, info};
use ruma::{
api::{
client::r0::sync::sync_events,
federation::discovery::{ServerSigningKeys, VerifyKey},
},
DeviceId, EventId, MilliSecondsSinceUnixEpoch, ServerName, ServerSigningKeyId, UserId,
api::federation::discovery::{ServerSigningKeys, VerifyKey},
ServerName, ServerSigningKeyId,
};
use rustls::{ServerCertVerifier, WebPKIVerifier};
use std::{
collections::{BTreeMap, HashMap},
fs,
path::PathBuf,
sync::{Arc, RwLock},
time::{Duration, Instant},
time::Duration,
};
use tokio::sync::Semaphore;
use trust_dns_resolver::TokioAsyncResolver;
use super::abstraction::Tree;
pub const COUNTER: &[u8] = b"c";
pub const COUNTER: &str = "c";
type WellKnownMap = HashMap<Box<ServerName>, (String, String)>;
type TlsNameMap = HashMap<String, webpki::DNSName>;
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
#[derive(Clone)]
pub struct Globals {
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
pub tls_name_override: Arc<RwLock<TlsNameMap>>,
pub(super) globals: Arc<dyn Tree>,
pub(super) globals: sled::Tree,
config: Config,
keypair: Arc<ruma::signatures::Ed25519KeyPair>,
reqwest_client: reqwest::Client,
dns_resolver: TokioAsyncResolver,
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
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 sync_receivers: RwLock<
BTreeMap<
(UserId, Box<DeviceId>),
(
Option<String>,
tokio::sync::watch::Receiver<Option<ConduitResult<sync_events::Response>>>,
), // since, rx
>,
>,
pub(super) servertimeout_signingkey: sled::Tree, // ServerName + Timeout Timestamp -> algorithm:key + pubkey
}
struct MatrixServerVerifier {
@@ -84,20 +64,15 @@ impl ServerCertVerifier for MatrixServerVerifier {
impl Globals {
pub fn load(
globals: Arc<dyn Tree>,
server_signingkeys: Arc<dyn Tree>,
globals: sled::Tree,
servertimeout_signingkey: sled::Tree,
config: Config,
) -> Result<Self> {
let keypair_bytes = globals.get(b"keypair")?.map_or_else(
|| {
let keypair = utils::generate_keypair();
globals.insert(b"keypair", &keypair)?;
Ok::<_, Error>(keypair)
},
|s| Ok(s.to_vec()),
)?;
let bytes = &*globals
.update_and_fetch("keypair", utils::generate_keypair)?
.expect("utils::generate_keypair always returns Some");
let mut parts = keypair_bytes.splitn(2, |&b| b == 0xff);
let mut parts = bytes.splitn(2, |&b| b == 0xff);
let keypair = utils::string_from_bytes(
// 1. version
@@ -114,7 +89,7 @@ impl Globals {
.map(|key| (version, key))
})
.and_then(|(version, key)| {
ruma::signatures::Ed25519KeyPair::from_der(&key, version)
ruma::signatures::Ed25519KeyPair::new(&key, version)
.map_err(|_| Error::bad_database("Private or public keys are invalid."))
});
@@ -122,7 +97,7 @@ impl Globals {
Ok(k) => k,
Err(e) => {
error!("Keypair invalid. Deleting...");
globals.remove(b"keypair")?;
globals.remove("keypair")?;
return Err(e);
}
};
@@ -150,7 +125,7 @@ impl Globals {
.as_ref()
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static());
let s = Self {
Ok(Self {
globals,
config,
keypair: Arc::new(keypair),
@@ -160,17 +135,9 @@ impl Globals {
})?,
actual_destination_cache: Arc::new(RwLock::new(WellKnownMap::new())),
tls_name_override,
server_signingkeys,
servertimeout_signingkey,
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())),
sync_receivers: RwLock::new(BTreeMap::new()),
};
fs::create_dir_all(s.get_media_folder())?;
Ok(s)
})
}
/// Returns this server's keypair.
@@ -184,14 +151,19 @@ impl Globals {
}
pub fn next_count(&self) -> Result<u64> {
utils::u64_from_bytes(&self.globals.increment(COUNTER)?)
.map_err(|_| Error::bad_database("Count has invalid bytes."))
Ok(utils::u64_from_bytes(
&self
.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> {
self.globals.get(COUNTER)?.map_or(Ok(0_u64), |bytes| {
utils::u64_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Count has invalid bytes."))
Ok(utils::u64_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Count has invalid bytes."))?)
})
}
@@ -231,29 +203,30 @@ impl Globals {
/// Remove the outdated keys and insert the new ones.
///
/// This doesn't actually check that the keys provided are newer than the old set.
pub fn add_signing_key(&self, origin: &ServerName, new_keys: ServerSigningKeys) -> Result<()> {
// Not atomic, but this is not critical
let signingkeys = self.server_signingkeys.get(origin.as_bytes())?;
pub fn add_signing_key(&self, origin: &ServerName, keys: &ServerSigningKeys) -> Result<()> {
let mut key1 = origin.as_bytes().to_vec();
key1.push(0xff);
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 mut key2 = key1.clone();
let ServerSigningKeys {
verify_keys,
old_verify_keys,
..
} = new_keys;
let ts = keys
.valid_until_ts
.duration_since(std::time::UNIX_EPOCH)
.expect("time is valid")
.as_millis() as u64;
keys.verify_keys.extend(verify_keys.into_iter());
keys.old_verify_keys.extend(old_verify_keys.into_iter());
key1.extend_from_slice(&ts.to_be_bytes());
key2.extend_from_slice(&(ts + 1).to_be_bytes());
self.server_signingkeys.insert(
origin.as_bytes(),
&serde_json::to_vec(&keys).expect("serversigningkeys can be serialized"),
self.servertimeout_signingkey.insert(
key1,
serde_json::to_vec(&keys.verify_keys).expect("ServerSigningKeys are a valid string"),
)?;
self.servertimeout_signingkey.insert(
key2,
serde_json::to_vec(&keys.old_verify_keys)
.expect("ServerSigningKeys are a valid string"),
)?;
Ok(())
@@ -264,49 +237,25 @@ impl Globals {
&self,
origin: &ServerName,
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
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 mut response = BTreeMap::new();
Ok(signingkeys)
}
let now = crate::utils::millis_since_unix_epoch();
pub fn database_version(&self) -> Result<u64> {
self.globals.get(b"version")?.map_or(Ok(0), |version| {
utils::u64_from_bytes(&version)
.map_err(|_| Error::bad_database("Database version id is invalid."))
})
}
pub fn bump_database_version(&self, new_version: u64) -> Result<()> {
self.globals
.insert(b"version", &new_version.to_be_bytes())?;
Ok(())
}
pub fn get_media_folder(&self) -> PathBuf {
let mut r = PathBuf::new();
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
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
let (k, bytes) = item?;
let valid_until = k
.splitn(2, |&b| b == 0xff)
.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"))?;
// If these keys are still valid use em!
if valid_until > now {
let btree: BTreeMap<_, _> = serde_json::from_slice(&bytes)
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"))?;
response.extend(btree);
}
}
Ok(response)
}
}
+75 -65
View File
@@ -6,14 +6,13 @@ use ruma::{
},
RoomId, UserId,
};
use std::{collections::BTreeMap, convert::TryFrom, sync::Arc};
use super::abstraction::Tree;
use std::{collections::BTreeMap, convert::TryFrom};
#[derive(Clone)]
pub struct KeyBackups {
pub(super) backupid_algorithm: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
pub(super) backupid_etag: Arc<dyn Tree>, // BackupId = UserId + Version(Count)
pub(super) backupkeyid_backup: Arc<dyn Tree>, // BackupKeyId = UserId + Version + RoomId + SessionId
pub(super) backupid_algorithm: sled::Tree, // BackupId = UserId + Version(Count)
pub(super) backupid_etag: sled::Tree, // BackupId = UserId + Version(Count)
pub(super) backupkeyid_backup: sled::Tree, // BackupKeyId = UserId + Version + RoomId + SessionId
}
impl KeyBackups {
@@ -31,7 +30,8 @@ impl KeyBackups {
self.backupid_algorithm.insert(
&key,
&serde_json::to_vec(backup_metadata).expect("BackupAlgorithm::to_vec always works"),
&*serde_json::to_string(backup_metadata)
.expect("BackupAlgorithm::to_string always works"),
)?;
self.backupid_etag
.insert(&key, &globals.next_count()?.to_be_bytes())?;
@@ -48,8 +48,13 @@ impl KeyBackups {
key.push(0xff);
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
self.backupkeyid_backup.remove(&outdated_key)?;
for outdated_key in self
.backupkeyid_backup
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
}
Ok(())
@@ -75,9 +80,8 @@ impl KeyBackups {
self.backupid_algorithm.insert(
&key,
&serde_json::to_string(backup_metadata)
.expect("BackupAlgorithm::to_string always works")
.as_bytes(),
&*serde_json::to_string(backup_metadata)
.expect("BackupAlgorithm::to_string always works"),
)?;
self.backupid_etag
.insert(&key, &globals.next_count()?.to_be_bytes())?;
@@ -87,14 +91,11 @@ impl KeyBackups {
pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff);
let mut last_possible_key = prefix.clone();
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
self.backupid_algorithm
.iter_from(&last_possible_key, true)
.take_while(move |(k, _)| k.starts_with(&prefix))
.next()
.map_or(Ok(None), |(key, value)| {
.scan_prefix(&prefix)
.last()
.map_or(Ok(None), |r| {
let (key, value) = r?;
let version = utils::string_from_bytes(
key.rsplit(|&b| b == 0xff)
.next()
@@ -116,12 +117,10 @@ impl KeyBackups {
key.push(0xff);
key.extend_from_slice(version.as_bytes());
self.backupid_algorithm
.get(&key)?
.map_or(Ok(None), |bytes| {
serde_json::from_slice(&bytes)
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))
})
self.backupid_algorithm.get(key)?.map_or(Ok(None), |bytes| {
Ok(serde_json::from_slice(&bytes)
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))?)
})
}
pub fn add_key(
@@ -154,7 +153,7 @@ impl KeyBackups {
self.backupkeyid_backup.insert(
&key,
&serde_json::to_vec(&key_data).expect("KeyBackupData::to_vec always works"),
&*serde_json::to_string(&key_data).expect("KeyBackupData::to_string always works"),
)?;
Ok(())
@@ -165,7 +164,7 @@ impl KeyBackups {
prefix.push(0xff);
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> {
@@ -195,37 +194,33 @@ impl KeyBackups {
let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new();
for result in self
.backupkeyid_backup
.scan_prefix(prefix)
.map(|(key, value)| {
let mut parts = key.rsplit(|&b| b == 0xff);
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| {
let (key, value) = r?;
let mut parts = key.rsplit(|&b| b == 0xff);
let session_id =
utils::string_from_bytes(&parts.next().ok_or_else(|| {
Error::bad_database("backupkeyid_backup key is invalid.")
})?)
.map_err(|_| {
Error::bad_database("backupkeyid_backup session_id is invalid.")
})?;
let session_id = utils::string_from_bytes(
&parts
.next()
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?,
)
.map_err(|_| Error::bad_database("backupkeyid_backup session_id is invalid."))?;
let room_id = RoomId::try_from(
utils::string_from_bytes(&parts.next().ok_or_else(|| {
Error::bad_database("backupkeyid_backup key is invalid.")
})?)
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?,
let room_id = RoomId::try_from(
utils::string_from_bytes(
&parts
.next()
.ok_or_else(|| Error::bad_database("backupkeyid_backup key 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."))?,
)
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?;
let key_data = serde_json::from_slice(&value).map_err(|_| {
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
})?;
let key_data = serde_json::from_slice(&value).map_err(|_| {
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?;
rooms
.entry(room_id)
@@ -244,7 +239,7 @@ impl KeyBackups {
user_id: &UserId,
version: &str,
room_id: &RoomId,
) -> Result<BTreeMap<String, KeyBackupData>> {
) -> BTreeMap<String, KeyBackupData> {
let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes());
@@ -252,10 +247,10 @@ impl KeyBackups {
prefix.extend_from_slice(room_id.as_bytes());
prefix.push(0xff);
Ok(self
.backupkeyid_backup
.scan_prefix(prefix)
.map(|(key, value)| {
self.backupkeyid_backup
.scan_prefix(&prefix)
.map(|r| {
let (key, value) = r?;
let mut parts = key.rsplit(|&b| b == 0xff);
let session_id =
@@ -273,7 +268,7 @@ impl KeyBackups {
Ok::<_, Error>((session_id, key_data))
})
.filter_map(|r| r.ok())
.collect())
.collect()
}
pub fn get_session(
@@ -307,8 +302,13 @@ impl KeyBackups {
key.extend_from_slice(&version.as_bytes());
key.push(0xff);
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
self.backupkeyid_backup.remove(&outdated_key)?;
for outdated_key in self
.backupkeyid_backup
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
}
Ok(())
@@ -327,8 +327,13 @@ impl KeyBackups {
key.extend_from_slice(&room_id.as_bytes());
key.push(0xff);
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
self.backupkeyid_backup.remove(&outdated_key)?;
for outdated_key in self
.backupkeyid_backup
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
}
Ok(())
@@ -349,8 +354,13 @@ impl KeyBackups {
key.push(0xff);
key.extend_from_slice(&session_id.as_bytes());
for (outdated_key, _) in self.backupkeyid_backup.scan_prefix(key) {
self.backupkeyid_backup.remove(&outdated_key)?;
for outdated_key in self
.backupkeyid_backup
.scan_prefix(&key)
.keys()
.filter_map(|r| r.ok())
{
self.backupkeyid_backup.remove(outdated_key)?;
}
Ok(())
+48 -95
View File
@@ -1,28 +1,25 @@
use crate::database::globals::Globals;
use image::{imageops::FilterType, GenericImageView};
use super::abstraction::Tree;
use crate::{utils, Error, Result};
use std::{mem, sync::Arc};
use tokio::{fs::File, io::AsyncReadExt, io::AsyncWriteExt};
use std::mem;
pub struct FileMeta {
pub content_disposition: Option<String>,
pub filename: Option<String>,
pub content_type: Option<String>,
pub file: Vec<u8>,
}
#[derive(Clone)]
pub struct Media {
pub(super) mediaid_file: Arc<dyn Tree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
pub(super) mediaid_file: sled::Tree, // MediaId = MXC + WidthHeight + Filename + ContentType
}
impl Media {
/// Uploads a file.
pub async fn create(
/// Uploads or replaces a file.
pub fn create(
&self,
mxc: String,
globals: &Globals,
content_disposition: &Option<&str>,
filename: &Option<&str>,
content_type: &Option<&str>,
file: &[u8],
) -> Result<()> {
@@ -31,12 +28,7 @@ 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()); // Height = 0 if it's not a thumbnail
key.push(0xff);
key.extend_from_slice(
content_disposition
.as_ref()
.map(|f| f.as_bytes())
.unwrap_or_default(),
);
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
key.push(0xff);
key.extend_from_slice(
content_type
@@ -45,20 +37,16 @@ impl Media {
.unwrap_or_default(),
);
let path = globals.get_media_file(&key);
let mut f = File::create(path).await?;
f.write_all(file).await?;
self.mediaid_file.insert(key, file)?;
self.mediaid_file.insert(&key, &[])?;
Ok(())
}
/// Uploads or replaces a file thumbnail.
pub async fn upload_thumbnail(
pub fn upload_thumbnail(
&self,
mxc: String,
globals: &Globals,
content_disposition: &Option<String>,
filename: &Option<String>,
content_type: &Option<String>,
width: u32,
height: u32,
@@ -69,12 +57,7 @@ impl Media {
key.extend_from_slice(&width.to_be_bytes());
key.extend_from_slice(&height.to_be_bytes());
key.push(0xff);
key.extend_from_slice(
content_disposition
.as_ref()
.map(|f| f.as_bytes())
.unwrap_or_default(),
);
key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
key.push(0xff);
key.extend_from_slice(
content_type
@@ -83,59 +66,48 @@ impl Media {
.unwrap_or_default(),
);
let path = globals.get_media_file(&key);
let mut f = File::create(path).await?;
f.write_all(file).await?;
self.mediaid_file.insert(&key, &[])?;
self.mediaid_file.insert(key, file)?;
Ok(())
}
/// Downloads a file.
pub async fn get(&self, globals: &Globals, mxc: &str) -> Result<Option<FileMeta>> {
pub fn get(&self, mxc: &str) -> Result<Option<FileMeta>> {
let mut prefix = mxc.as_bytes().to_vec();
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()); // Height = 0 if it's not a thumbnail
prefix.push(0xff);
let mut iter = self.mediaid_file.scan_prefix(prefix);
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?;
if let Some(r) = self.mediaid_file.scan_prefix(&prefix).next() {
let (key, file) = r?;
let mut parts = key.rsplit(|&b| b == 0xff);
let content_type = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes).map_err(|_| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.")
})
})?)
})
.transpose()?;
let content_disposition_bytes = parts
let filename_bytes = parts
.next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let content_disposition = if content_disposition_bytes.is_empty() {
let filename = if filename_bytes.is_empty() {
None
} else {
Some(
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
Error::bad_database(
"Content Disposition in mediaid_file is invalid unicode.",
)
})?,
)
Some(utils::string_from_bytes(filename_bytes).map_err(|_| {
Error::bad_database("Filename in mediaid_file is invalid unicode.")
})?)
};
Ok(Some(FileMeta {
content_disposition,
filename,
content_type,
file,
file: file.to_vec(),
}))
} else {
Ok(None)
@@ -165,13 +137,7 @@ impl Media {
/// - 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.
pub async fn get_thumbnail(
&self,
mxc: String,
globals: &Globals,
width: u32,
height: u32,
) -> Result<Option<FileMeta>> {
pub fn get_thumbnail(&self, mxc: String, width: u32, height: u32) -> Result<Option<FileMeta>> {
let (width, height, crop) = self
.thumbnail_properties(width, height)
.unwrap_or((0, 0, false)); // 0, 0 because that's the original file
@@ -189,72 +155,63 @@ impl Media {
original_prefix.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
original_prefix.push(0xff);
if let Some((key, _)) = self.mediaid_file.scan_prefix(thumbnail_prefix).next() {
if let Some(r) = self.mediaid_file.scan_prefix(&thumbnail_prefix).next() {
// Using saved 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 content_type = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes).map_err(|_| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.")
})
})?)
})
.transpose()?;
let content_disposition_bytes = parts
let filename_bytes = parts
.next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let content_disposition = if content_disposition_bytes.is_empty() {
let filename = if filename_bytes.is_empty() {
None
} else {
Some(
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
Error::bad_database("Content Disposition in db is invalid.")
})?,
utils::string_from_bytes(filename_bytes)
.map_err(|_| Error::bad_database("Filename in db is invalid."))?,
)
};
Ok(Some(FileMeta {
content_disposition,
filename,
content_type,
file: file.to_vec(),
}))
} else if let Some((key, _)) = self.mediaid_file.scan_prefix(original_prefix).next() {
} else if let Some(r) = self.mediaid_file.scan_prefix(&original_prefix).next() {
// 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 content_type = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes).map_err(|_| {
Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
Error::bad_database("Content type in mediaid_file is invalid unicode.")
})
})?)
})
.transpose()?;
let content_disposition_bytes = parts
let filename_bytes = parts
.next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
let content_disposition = if content_disposition_bytes.is_empty() {
let filename = if filename_bytes.is_empty() {
None
} else {
Some(
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
Error::bad_database(
"Content Disposition in mediaid_file is invalid unicode.",
)
})?,
)
Some(utils::string_from_bytes(filename_bytes).map_err(|_| {
Error::bad_database("Filename in mediaid_file is invalid unicode.")
})?)
};
if let Ok(image) = image::load_from_memory(&file) {
@@ -262,7 +219,7 @@ impl Media {
let original_height = image.height();
if width > original_width || height > original_height {
return Ok(Some(FileMeta {
content_disposition,
filename,
content_type,
file: file.to_vec(),
}));
@@ -326,21 +283,17 @@ impl Media {
widthheight,
);
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, &[])?;
self.mediaid_file.insert(thumbnail_key, &*thumbnail_bytes)?;
Ok(Some(FileMeta {
content_disposition,
filename,
content_type,
file: thumbnail_bytes.to_vec(),
}))
} else {
// Couldn't parse file to generate thumbnail, send original
Ok(Some(FileMeta {
content_disposition,
filename,
content_type,
file: file.to_vec(),
}))
+44 -36
View File
@@ -14,17 +14,23 @@ use ruma::{
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
uint, UInt, UserId,
};
use sled::IVec;
use std::{convert::TryFrom, fmt::Debug, mem, sync::Arc};
use super::abstraction::Tree;
use std::{convert::TryFrom, fmt::Debug};
#[derive(Debug, Clone)]
pub struct PushData {
/// UserId + pushkey -> Pusher
pub(super) senderkey_pusher: Arc<dyn Tree>,
pub(super) senderkey_pusher: sled::Tree,
}
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<()> {
let mut key = sender.as_bytes().to_vec();
key.push(0xff);
@@ -34,14 +40,14 @@ impl PushData {
if pusher.kind.is_none() {
return self
.senderkey_pusher
.remove(&key)
.remove(key)
.map(|_| ())
.map_err(Into::into);
}
self.senderkey_pusher.insert(
&key,
&serde_json::to_vec(&pusher).expect("Pusher is valid JSON value"),
key,
&*serde_json::to_string(&pusher).expect("Pusher is valid JSON string"),
)?;
Ok(())
@@ -51,8 +57,8 @@ impl PushData {
self.senderkey_pusher
.get(senderkey)?
.map(|push| {
serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))
Ok(serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
})
.transpose()
}
@@ -63,21 +69,23 @@ impl PushData {
self.senderkey_pusher
.scan_prefix(prefix)
.map(|(_, push)| {
serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))
.values()
.map(|push| {
let push = push.map_err(|_| Error::bad_database("Invalid push bytes in db."))?;
Ok(serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
})
.collect()
}
pub fn get_pusher_senderkeys<'a>(
&'a self,
sender: &UserId,
) -> impl Iterator<Item = Box<[u8]>> + 'a {
pub fn get_pusher_senderkeys(&self, sender: &UserId) -> impl Iterator<Item = Result<IVec>> {
let mut prefix = sender.as_bytes().to_vec();
prefix.push(0xff);
self.senderkey_pusher.scan_prefix(prefix).map(|(k, _)| k)
self.senderkey_pusher
.scan_prefix(prefix)
.keys()
.map(|r| Ok(r?))
}
}
@@ -106,23 +114,24 @@ where
//*reqwest_request.timeout_mut() = Some(Duration::from_secs(5));
let url = reqwest_request.url().clone();
let response = globals.reqwest_client().execute(reqwest_request).await;
let reqwest_response = globals.reqwest_client().execute(reqwest_request).await;
match response {
Ok(mut response) => {
// reqwest::Response -> http::Response conversion
let status = response.status();
let mut http_response_builder = http::Response::builder()
.status(status)
.version(response.version());
mem::swap(
response.headers_mut(),
http_response_builder
.headers_mut()
.expect("http::response::Builder is usable"),
);
// Because reqwest::Response -> http::Response is complicated:
match reqwest_response {
Ok(mut reqwest_response) => {
let status = reqwest_response.status();
let mut http_response = http::Response::builder().status(status);
let headers = http_response.headers_mut().unwrap();
let body = response.bytes().await.unwrap_or_else(|e| {
for (k, v) in reqwest_response.headers_mut().drain() {
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);
Vec::new().into()
}); // TODO: handle timeout
@@ -138,7 +147,7 @@ where
}
let response = T::IncomingResponse::try_from_http_response(
http_response_builder
http_response
.body(body)
.expect("reqwest body is valid http body"),
);
@@ -203,7 +212,7 @@ pub fn get_actions<'a>(
.rooms
.room_state_get(&pdu.room_id, &EventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_value(ev.content.clone())
serde_json::from_value(ev.content)
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
})
.transpose()?
@@ -286,8 +295,7 @@ async fn send_notice(
} else {
notifi.sender = Some(&event.sender);
notifi.event_type = Some(&event.kind);
let content = serde_json::value::to_raw_value(&event.content).ok();
notifi.content = content.as_deref();
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
if event.kind == EventType::RoomMember {
notifi.user_is_target = event.state_key.as_deref() == Some(event.sender.as_str());
+338 -362
View File
File diff suppressed because it is too large Load Diff
+64 -120
View File
@@ -1,29 +1,28 @@
use crate::{database::abstraction::Tree, utils, Error, Result};
use crate::{utils, Error, Result};
use ruma::{
events::{
presence::{PresenceEvent, PresenceEventContent},
AnyEphemeralRoomEvent, SyncEphemeralRoomEvent,
AnyEvent as EduEvent, SyncEphemeralRoomEvent,
},
presence::PresenceState,
serde::Raw,
signatures::CanonicalJsonObject,
RoomId, UInt, UserId,
};
use std::{
collections::{HashMap, HashSet},
convert::{TryFrom, TryInto},
mem,
sync::Arc,
};
#[derive(Clone)]
pub struct RoomEdus {
pub(in super::super) readreceiptid_readreceipt: Arc<dyn Tree>, // ReadReceiptId = RoomId + Count + UserId
pub(in super::super) roomuserid_privateread: Arc<dyn Tree>, // RoomUserId = Room + User, PrivateRead = Count
pub(in super::super) roomuserid_lastprivatereadupdate: Arc<dyn Tree>, // LastPrivateReadUpdate = Count
pub(in super::super) typingid_userid: Arc<dyn Tree>, // TypingId = RoomId + TimeoutTime + Count
pub(in super::super) roomid_lasttypingupdate: Arc<dyn Tree>, // LastRoomTypingUpdate = Count
pub(in super::super) presenceid_presence: Arc<dyn Tree>, // PresenceId = RoomId + Count + UserId
pub(in super::super) userid_lastpresenceupdate: Arc<dyn Tree>, // LastPresenceUpdate = Count
pub(in super::super) readreceiptid_readreceipt: sled::Tree, // ReadReceiptId = RoomId + Count + UserId
pub(in super::super) roomuserid_privateread: sled::Tree, // RoomUserId = Room + User, PrivateRead = Count
pub(in super::super) roomuserid_lastprivatereadupdate: sled::Tree, // LastPrivateReadUpdate = Count
pub(in super::super) typingid_userid: sled::Tree, // TypingId = RoomId + TimeoutTime + Count
pub(in super::super) roomid_lasttypingupdate: sled::Tree, // LastRoomTypingUpdate = Count
pub(in super::super) presenceid_presence: sled::Tree, // PresenceId = RoomId + Count + UserId
pub(in super::super) userid_lastpresenceupdate: sled::Tree, // LastPresenceUpdate = Count
}
impl RoomEdus {
@@ -32,21 +31,21 @@ impl RoomEdus {
&self,
user_id: &UserId,
room_id: &RoomId,
event: AnyEphemeralRoomEvent,
event: EduEvent,
globals: &super::super::globals::Globals,
) -> Result<()> {
let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff);
let mut last_possible_key = prefix.clone();
last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes());
// Remove old entry
if let Some((old, _)) = self
if let Some(old) = self
.readreceiptid_readreceipt
.iter_from(&last_possible_key, true)
.take_while(|(key, _)| key.starts_with(&prefix))
.find(|(key, _)| {
.scan_prefix(&prefix)
.keys()
.rev()
.filter_map(|r| r.ok())
.take_while(|key| key.starts_with(&prefix))
.find(|key| {
key.rsplit(|&b| b == 0xff)
.next()
.expect("rsplit always returns an element")
@@ -54,7 +53,7 @@ impl RoomEdus {
})
{
// This is the old room_latest
self.readreceiptid_readreceipt.remove(&old)?;
self.readreceiptid_readreceipt.remove(old)?;
}
let mut room_latest_id = prefix;
@@ -63,8 +62,8 @@ impl RoomEdus {
room_latest_id.extend_from_slice(&user_id.as_bytes());
self.readreceiptid_readreceipt.insert(
&room_latest_id,
&serde_json::to_vec(&event).expect("EduEvent::to_string always works"),
room_latest_id,
&*serde_json::to_string(&event).expect("EduEvent::to_string always works"),
)?;
Ok(())
@@ -72,47 +71,27 @@ impl RoomEdus {
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
#[tracing::instrument(skip(self))]
pub fn readreceipts_since<'a>(
&'a self,
pub fn readreceipts_since(
&self,
room_id: &RoomId,
since: u64,
) -> impl Iterator<Item = Result<(UserId, u64, Raw<ruma::events::AnySyncEphemeralRoomEvent>)>> + 'a
{
) -> Result<impl Iterator<Item = Result<Raw<ruma::events::AnySyncEphemeralRoomEvent>>>> {
let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff);
let prefix2 = 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
self.readreceiptid_readreceipt
.iter_from(&first_possible_edu, false)
.take_while(move |(k, _)| k.starts_with(&prefix2))
.map(move |(k, v)| {
let count =
utils::u64_from_bytes(&k[prefix.len()..prefix.len() + mem::size_of::<u64>()])
.map_err(|_| Error::bad_database("Invalid readreceiptid count in db."))?;
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"),
),
))
})
Ok(self
.readreceiptid_readreceipt
.range(&*first_possible_edu..)
.filter_map(|r| r.ok())
.take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(_, v)| {
Ok(serde_json::from_slice(&v).map_err(|_| {
Error::bad_database("Read receipt in roomlatestid_roomlatest is invalid.")
})?)
}))
}
/// Sets a private read marker at `count`.
@@ -143,13 +122,11 @@ impl RoomEdus {
key.push(0xff);
key.extend_from_slice(&user_id.as_bytes());
self.roomuserid_privateread
.get(&key)?
.map_or(Ok(None), |v| {
Ok(Some(utils::u64_from_bytes(&v).map_err(|_| {
Error::bad_database("Invalid private read marker bytes")
})?))
})
self.roomuserid_privateread.get(key)?.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.
@@ -214,10 +191,11 @@ impl RoomEdus {
// Maybe there are multiple ones from calling roomtyping_add multiple times
for outdated_edu in self
.typingid_userid
.scan_prefix(prefix)
.filter(|(_, v)| &**v == user_id.as_bytes())
.scan_prefix(&prefix)
.filter_map(|r| r.ok())
.filter(|(_, v)| v == user_id.as_bytes())
{
self.typingid_userid.remove(&outdated_edu.0)?;
self.typingid_userid.remove(outdated_edu.0)?;
found_outdated = true;
}
@@ -245,8 +223,10 @@ impl RoomEdus {
// Find all outdated edus before inserting a new one
for outdated_edu in self
.typingid_userid
.scan_prefix(prefix)
.map(|(key, _)| {
.scan_prefix(&prefix)
.keys()
.map(|key| {
let key = key?;
Ok::<_, Error>((
key.clone(),
utils::u64_from_bytes(
@@ -261,7 +241,7 @@ impl RoomEdus {
.take_while(|&(_, timestamp)| timestamp < current_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;
}
@@ -305,11 +285,14 @@ impl RoomEdus {
for user_id in self
.typingid_userid
.scan_prefix(prefix)
.map(|(_, user_id)| {
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."))
.values()
.map(|user_id| {
Ok::<_, Error>(
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."))?,
)
})
{
user_ids.insert(user_id?);
@@ -344,12 +327,12 @@ impl RoomEdus {
presence_id.extend_from_slice(&presence.sender.as_bytes());
self.presenceid_presence.insert(
&presence_id,
&serde_json::to_vec(&presence).expect("PresenceEvent can be serialized"),
presence_id,
&*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"),
)?;
self.userid_lastpresenceupdate.insert(
user_id.as_bytes(),
&user_id.as_bytes(),
&utils::millis_since_unix_epoch().to_be_bytes(),
)?;
@@ -379,47 +362,6 @@ impl RoomEdus {
.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.
pub fn presence_maintain(
&self,
@@ -431,6 +373,7 @@ impl RoomEdus {
for (user_id_bytes, last_timestamp) in self
.userid_lastpresenceupdate
.iter()
.filter_map(|r| r.ok())
.filter_map(|(k, bytes)| {
Some((
k,
@@ -460,8 +403,8 @@ impl RoomEdus {
presence_id.extend_from_slice(&user_id_bytes);
self.presenceid_presence.insert(
&presence_id,
&serde_json::to_vec(&PresenceEvent {
presence_id,
&*serde_json::to_string(&PresenceEvent {
content: PresenceEventContent {
avatar_url: None,
currently_active: None,
@@ -507,7 +450,8 @@ impl RoomEdus {
for (key, value) in self
.presenceid_presence
.iter_from(&*first_possible_edu, false)
.range(&*first_possible_edu..)
.filter_map(|r| r.ok())
.take_while(|(key, _)| key.starts_with(&prefix))
{
let user_id = UserId::try_from(
+271 -366
View File
@@ -1,9 +1,9 @@
use std::{
collections::{BTreeMap, HashMap},
collections::HashMap,
convert::{TryFrom, TryInto},
fmt::Debug,
sync::Arc,
time::{Duration, Instant},
time::{Duration, Instant, SystemTime},
};
use crate::{
@@ -12,28 +12,15 @@ use crate::{
use federation::transactions::send_transaction_message;
use log::{error, warn};
use ring::digest;
use rocket::futures::{
channel::mpsc,
stream::{FuturesUnordered, StreamExt},
};
use rocket::futures::stream::{FuturesUnordered, StreamExt};
use ruma::{
api::{
appservice,
federation::{
self,
transactions::edu::{Edu, ReceiptContent, ReceiptData, ReceiptMap},
},
OutgoingRequest,
},
events::{push_rules, AnySyncEphemeralRoomEvent, EventType},
push,
receipt::ReceiptType,
MilliSecondsSinceUnixEpoch, ServerName, UInt, UserId,
api::{appservice, federation, OutgoingRequest},
events::{push_rules, EventType},
push, ServerName, UInt, UserId,
};
use sled::IVec;
use tokio::{select, sync::Semaphore};
use super::abstraction::Tree;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OutgoingKind {
Appservice(Box<ServerName>),
@@ -41,46 +28,12 @@ pub enum OutgoingKind {
Normal(Box<ServerName>),
}
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>),
}
#[derive(Clone)]
pub struct Sending {
/// The state for a given state hash.
pub(super) servername_educount: Arc<dyn Tree>, // EduCount: Count of last EDU sync
pub(super) servernamepduids: Arc<dyn Tree>, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
pub(super) servercurrentevents: Arc<dyn Tree>, // ServerCurrentEvents = (+ / $)ServerName / UserId + PduId / (*)EduEvent
pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+ / $)ServerName / UserId + PduId
pub(super) maximum_requests: Arc<Semaphore>,
pub sender: mpsc::UnboundedSender<Vec<u8>>,
}
enum TransactionStatus {
@@ -90,23 +43,28 @@ enum TransactionStatus {
}
impl Sending {
pub fn start_handler(&self, db: Arc<Database>, mut receiver: mpsc::UnboundedReceiver<Vec<u8>>) {
pub fn start_handler(&self, db: &Database) {
let servernamepduids = self.servernamepduids.clone();
let servercurrentpdus = self.servercurrentpdus.clone();
let db = db.clone();
tokio::spawn(async move {
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();
// Retry requests we could not finish yet
let mut initial_transactions = HashMap::<OutgoingKind, Vec<SendingEventType>>::new();
for (key, outgoing_kind, event) in
db.sending
.servercurrentevents
.iter()
.filter_map(|(key, _)| {
Self::parse_servercurrentevent(&key)
.ok()
.map(|(k, e)| (key, k, e))
})
let mut initial_transactions = HashMap::<OutgoingKind, Vec<Vec<u8>>>::new();
for (key, outgoing_kind, pdu) in servercurrentpdus
.iter()
.filter_map(|r| r.ok())
.filter_map(|(key, _)| {
Self::parse_servercurrentpdus(&key)
.ok()
.map(|(k, p)| (key, k, p.to_vec()))
})
{
let entry = initial_transactions
.entry(outgoing_kind.clone())
@@ -114,20 +72,39 @@ impl Sending {
if entry.len() > 30 {
warn!(
"Dropping some current events: {:?} {:?} {:?}",
key, outgoing_kind, event
"Dropping some current pdu: {:?} {:?} {:?}",
key, outgoing_kind, pdu
);
db.sending.servercurrentevents.remove(&key).unwrap();
servercurrentpdus.remove(key).unwrap();
continue;
}
entry.push(event);
entry.push(pdu);
}
for (outgoing_kind, events) in initial_transactions {
current_transaction_status
.insert(outgoing_kind.get_prefix(), TransactionStatus::Running);
futures.push(Self::handle_events(outgoing_kind.clone(), events, &db));
for (outgoing_kind, pdus) in initial_transactions {
let mut prefix = match &outgoing_kind {
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);
current_transaction_status.insert(prefix, TransactionStatus::Running);
futures.push(Self::handle_event(outgoing_kind.clone(), pdus, &db));
}
loop {
@@ -135,42 +112,58 @@ impl Sending {
Some(response) = futures.next() => {
match response {
Ok(outgoing_kind) => {
let prefix = outgoing_kind.get_prefix();
for (key, _) in db.sending.servercurrentevents
.scan_prefix(prefix.clone())
let mut prefix = match &outgoing_kind {
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![];
p.extend_from_slice(server.as_bytes());
p
},
};
prefix.push(0xff);
for key in servercurrentpdus
.scan_prefix(&prefix)
.keys()
.filter_map(|r| r.ok())
{
db.sending.servercurrentevents.remove(&key).unwrap();
servercurrentpdus.remove(key).unwrap();
}
// Find events that have been added since starting the last request
let new_events = db.sending.servernamepduids
.scan_prefix(prefix.clone())
.map(|(k, _)| {
SendingEventType::Pdu(k[prefix.len()..].to_vec())
let new_pdus = servernamepduids
.scan_prefix(&prefix)
.keys()
.filter_map(|r| r.ok())
.map(|k| {
k[prefix.len()..].to_vec()
})
.take(30)
.collect::<Vec<_>>();
// TODO: find edus
if !new_events.is_empty() {
// Insert pdus we found
for event in &new_events {
if !new_pdus.is_empty() {
for pdu_id in &new_pdus {
let mut current_key = prefix.clone();
match event {
SendingEventType::Pdu(b) |
SendingEventType::Edu(b) => {
current_key.extend_from_slice(&b);
db.sending.servercurrentevents.insert(&current_key, &[]).unwrap();
db.sending.servernamepduids.remove(&current_key).unwrap();
}
}
current_key.extend_from_slice(pdu_id);
servercurrentpdus.insert(&current_key, &[]).unwrap();
servernamepduids.remove(&current_key).unwrap();
}
futures.push(
Self::handle_events(
Self::handle_event(
outgoing_kind.clone(),
new_events,
new_pdus,
&db,
)
);
@@ -179,7 +172,29 @@ impl Sending {
}
}
Err((outgoing_kind, _)) => {
current_transaction_status.entry(outgoing_kind.get_prefix()).and_modify(|e| *e = match e {
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);
current_transaction_status.entry(prefix).and_modify(|e| *e = match e {
TransactionStatus::Running => TransactionStatus::Failed(1, Instant::now()),
TransactionStatus::Retrying(n) => TransactionStatus::Failed(*n+1, Instant::now()),
TransactionStatus::Failed(_, _) => {
@@ -190,15 +205,98 @@ impl Sending {
}
};
},
Some(key) = receiver.next() => {
if let Ok((outgoing_kind, event)) = Self::parse_servercurrentevent(&key) {
if let Ok(Some(events)) = Self::select_events(
&outgoing_kind,
vec![(event, key)],
&mut current_transaction_status,
&db
) {
futures.push(Self::handle_events(outgoing_kind, events, &db));
Some(event) = &mut subscriber => {
if let sled::Event::Insert { key, .. } = event {
// New sled version:
//for (_tree, key, value_opt) in &event {
// if value_opt.is_none() {
// continue;
// }
let servernamepduid = key.clone();
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,
)
);
}
}
}
@@ -207,163 +305,13 @@ 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))]
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: Box<[u8]>) -> Result<()> {
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: IVec) -> Result<()> {
let mut key = b"$".to_vec();
key.extend_from_slice(&senderkey);
key.push(0xff);
key.extend_from_slice(pdu_id);
self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
self.servernamepduids.insert(key, b"")?;
Ok(())
}
@@ -373,8 +321,7 @@ impl Sending {
let mut key = server.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(pdu_id);
self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
self.servernamepduids.insert(key, b"")?;
Ok(())
}
@@ -385,14 +332,13 @@ impl Sending {
key.extend_from_slice(appservice_id.as_bytes());
key.push(0xff);
key.extend_from_slice(pdu_id);
self.servernamepduids.insert(&key, b"")?;
self.sender.unbounded_send(key).unwrap();
self.servernamepduids.insert(key, b"")?;
Ok(())
}
#[tracing::instrument]
fn calculate_hash(keys: &[&[u8]]) -> Vec<u8> {
fn calculate_hash(keys: &[Vec<u8>]) -> Vec<u8> {
// We only hash the pdu's event ids, not the whole pdu
let bytes = keys.join(&0xff);
let hash = digest::digest(&digest::SHA256, &bytes);
@@ -400,37 +346,33 @@ impl Sending {
}
#[tracing::instrument(skip(db))]
async fn handle_events(
async fn handle_event(
kind: OutgoingKind,
events: Vec<SendingEventType>,
pdu_ids: Vec<Vec<u8>>,
db: &Database,
) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> {
match &kind {
OutgoingKind::Appservice(server) => {
let mut pdu_jsons = Vec::new();
for event in &events {
match event {
SendingEventType::Pdu(pdu_id) => {
pdu_jsons.push(db.rooms
.get_pdu_from_id(&pdu_id)
.map_err(|e| (kind.clone(), e))?
let pdu_jsons = pdu_ids
.iter()
.map(|pdu_id| {
Ok::<_, (Box<ServerName>, Error)>(
db.rooms
.get_pdu_from_id(pdu_id)
.map_err(|e| (server.clone(), e))?
.ok_or_else(|| {
(
kind.clone(),
server.clone(),
Error::bad_database(
"[Appservice] Event in servernamepduids not found in db.",
),
)
})?
.to_room_event())
}
SendingEventType::Edu(_) => {
// Appservices don't need EDUs (?)
}
}
}
.to_any_event(),
)
})
.filter_map(|r| r.ok())
.collect::<Vec<_>>();
let permit = db.sending.maximum_requests.acquire().await;
let response = appservice_server::send_request(
@@ -442,14 +384,7 @@ impl Sending {
appservice::event::push_events::v1::Request {
events: &pdu_jsons,
txn_id: &base64::encode_config(
Self::calculate_hash(
&events
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
})
.collect::<Vec<_>>(),
),
Self::calculate_hash(&pdu_ids),
base64::URL_SAFE_NO_PAD,
),
},
@@ -463,30 +398,25 @@ impl Sending {
response
}
OutgoingKind::Push(user, pushkey) => {
let mut pdus = Vec::new();
for event in &events {
match event {
SendingEventType::Pdu(pdu_id) => {
pdus.push(
db.rooms
.get_pdu_from_id(&pdu_id)
.map_err(|e| (kind.clone(), e))?
.ok_or_else(|| {
(
kind.clone(),
Error::bad_database(
"[Push] Event in servernamepduids not found in db.",
),
)
})?,
);
}
SendingEventType::Edu(_) => {
// Push gateways don't need EDUs (?)
}
}
}
let pdus = pdu_ids
.iter()
.map(|pdu_id| {
Ok::<_, (Vec<u8>, Error)>(
db.rooms
.get_pdu_from_id(pdu_id)
.map_err(|e| (pushkey.clone(), e))?
.ok_or_else(|| {
(
pushkey.clone(),
Error::bad_database(
"[Push] Event in servernamepduids not found in db.",
),
)
})?,
)
})
.filter_map(|r| r.ok())
.collect::<Vec<_>>();
for pdu in pdus {
// Redacted events are not notification targets (we don't send push for them)
@@ -497,13 +427,13 @@ impl Sending {
let userid =
UserId::try_from(utils::string_from_bytes(user).map_err(|_| {
(
kind.clone(),
OutgoingKind::Push(user.clone(), pushkey.clone()),
Error::bad_database("Invalid push user string in db."),
)
})?)
.map_err(|_| {
(
kind.clone(),
OutgoingKind::Push(user.clone(), pushkey.clone()),
Error::bad_database("Invalid push user id in db."),
)
})?;
@@ -554,17 +484,15 @@ impl Sending {
Ok(OutgoingKind::Push(user.clone(), pushkey.clone()))
}
OutgoingKind::Normal(server) => {
let mut edu_jsons = Vec::new();
let mut pdu_jsons = Vec::new();
for event in &events {
match event {
SendingEventType::Pdu(pdu_id) => {
let pdu_jsons = pdu_ids
.iter()
.map(|pdu_id| {
Ok::<_, (OutgoingKind, Error)>(
// TODO: check room version and remove event_id if needed
pdu_jsons.push(serde_json::from_str(
serde_json::from_str(
PduEvent::convert_to_outgoing_federation_event(
db.rooms
.get_pdu_json_from_id(&pdu_id)
.get_pdu_json_from_id(pdu_id)
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))?
.ok_or_else(|| {
(
@@ -578,15 +506,11 @@ impl Sending {
.json()
.get(),
)
.expect("Raw<..> is always valid"));
}
SendingEventType::Edu(edu) => {
edu_jsons.push(
serde_json::from_slice(edu).expect("Raw<..> is always valid"),
);
}
}
}
.expect("Raw<..> is always valid"),
)
})
.filter_map(|r| r.ok())
.collect::<Vec<_>>();
let permit = db.sending.maximum_requests.acquire().await;
@@ -596,17 +520,10 @@ impl Sending {
send_transaction_message::v1::Request {
origin: db.globals.server_name(),
pdus: &pdu_jsons,
edus: &edu_jsons,
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
edus: &[],
origin_server_ts: SystemTime::now(),
transaction_id: &base64::encode_config(
Self::calculate_hash(
&events
.iter()
.map(|e| match e {
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
})
.collect::<Vec<_>>(),
),
Self::calculate_hash(&pdu_ids),
base64::URL_SAFE_NO_PAD,
),
},
@@ -629,13 +546,13 @@ impl Sending {
}
}
fn parse_servercurrentevent(key: &[u8]) -> Result<(OutgoingKind, SendingEventType)> {
fn parse_servercurrentpdus(key: &IVec) -> Result<(OutgoingKind, IVec)> {
// Appservices start with a plus
Ok::<_, Error>(if key.starts_with(b"+") {
let mut parts = key[1..].splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element");
let event = parts
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| {
@@ -646,11 +563,7 @@ impl Sending {
OutgoingKind::Appservice(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction")
})?),
if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
IVec::from(pdu),
)
} else if key.starts_with(b"$") {
let mut parts = key[1..].splitn(3, |&b| b == 0xff);
@@ -659,22 +572,18 @@ impl Sending {
let pushkey = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let event = parts
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
(
OutgoingKind::Push(user.to_vec(), pushkey.to_vec()),
if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
IVec::from(pdu),
)
} else {
let mut parts = key.splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element");
let event = parts
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| {
@@ -685,11 +594,7 @@ impl Sending {
OutgoingKind::Normal(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction")
})?),
if event.starts_with(b"*") {
SendingEventType::Edu(event[1..].to_vec())
} else {
SendingEventType::Pdu(event.to_vec())
},
IVec::from(pdu),
)
})
}
+6 -8
View File
@@ -1,12 +1,10 @@
use std::sync::Arc;
use crate::Result;
use ruma::{DeviceId, UserId};
use sled::IVec;
use super::abstraction::Tree;
#[derive(Clone)]
pub struct TransactionIds {
pub(super) userdevicetxnid_response: Arc<dyn Tree>, // Response can be empty (/sendToDevice) or the event id (/send)
pub(super) userdevicetxnid_response: sled::Tree, // Response can be empty (/sendToDevice) or the event id (/send)
}
impl TransactionIds {
@@ -23,7 +21,7 @@ impl TransactionIds {
key.push(0xff);
key.extend_from_slice(txn_id.as_bytes());
self.userdevicetxnid_response.insert(&key, data)?;
self.userdevicetxnid_response.insert(key, data)?;
Ok(())
}
@@ -33,7 +31,7 @@ impl TransactionIds {
user_id: &UserId,
device_id: Option<&DeviceId>,
txn_id: &str,
) -> Result<Option<Vec<u8>>> {
) -> Result<Option<IVec>> {
let mut key = user_id.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(device_id.map(|d| d.as_bytes()).unwrap_or_default());
@@ -41,6 +39,6 @@ impl TransactionIds {
key.extend_from_slice(txn_id.as_bytes());
// If there's no entry, this is a new transaction
self.userdevicetxnid_response.get(&key)
Ok(self.userdevicetxnid_response.get(key)?)
}
}
+33 -103
View File
@@ -1,20 +1,15 @@
use std::sync::Arc;
use crate::{client_server::SESSION_ID_LENGTH, utils, Error, Result};
use crate::{Error, Result};
use ruma::{
api::client::{
error::ErrorKind,
r0::uiaa::{IncomingAuthData, UiaaInfo},
},
signatures::CanonicalJsonValue,
DeviceId, UserId,
};
use super::abstraction::Tree;
#[derive(Clone)]
pub struct Uiaa {
pub(super) userdevicesessionid_uiaainfo: Arc<dyn Tree>, // User-interactive authentication
pub(super) userdevicesessionid_uiaarequest: Arc<dyn Tree>, // UiaaRequest = canonical json value
pub(super) userdeviceid_uiaainfo: sled::Tree, // User-interactive authentication
}
impl Uiaa {
@@ -24,20 +19,8 @@ impl Uiaa {
user_id: &UserId,
device_id: &DeviceId,
uiaainfo: &UiaaInfo,
json_body: &CanonicalJsonValue,
) -> Result<()> {
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),
)
self.update_uiaa_session(user_id, device_id, Some(uiaainfo))
}
pub fn try_auth(
@@ -57,13 +40,11 @@ impl Uiaa {
{
let mut uiaainfo = session
.as_ref()
.map(|session| self.get_uiaa_session(&user_id, &device_id, session))
.map(|session| {
Ok::<_, Error>(self.get_uiaa_session(&user_id, &device_id, session)?)
})
.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
match &**kind {
"m.login.password" => {
@@ -149,96 +130,35 @@ impl Uiaa {
}
if !completed {
self.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
Some(&uiaainfo),
)?;
self.update_uiaa_session(user_id, device_id, Some(&uiaainfo))?;
return Ok((false, uiaainfo));
}
// UIAA was successful! Remove this session and return true
self.update_uiaa_session(
user_id,
device_id,
uiaainfo.session.as_ref().expect("session is always set"),
None,
)?;
self.update_uiaa_session(user_id, device_id, None)?;
Ok((true, uiaainfo))
} else {
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(
&self,
user_id: &UserId,
device_id: &DeviceId,
session: &str,
uiaainfo: Option<&UiaaInfo>,
) -> 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());
let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes());
if let Some(uiaainfo) = uiaainfo {
self.userdevicesessionid_uiaainfo.insert(
&userdevicesessionid,
&serde_json::to_vec(&uiaainfo).expect("UiaaInfo::to_vec always works"),
self.userdeviceid_uiaainfo.insert(
&userdeviceid,
&*serde_json::to_string(&uiaainfo).expect("UiaaInfo::to_string always works"),
)?;
} else {
self.userdevicesessionid_uiaainfo
.remove(&userdevicesessionid)?;
self.userdeviceid_uiaainfo.remove(&userdeviceid)?;
}
Ok(())
@@ -250,16 +170,14 @@ impl Uiaa {
device_id: &DeviceId,
session: &str,
) -> Result<UiaaInfo> {
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());
let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes());
let uiaainfo = serde_json::from_slice::<UiaaInfo>(
&self
.userdevicesessionid_uiaainfo
.get(&userdevicesessionid)?
.userdeviceid_uiaainfo
.get(&userdeviceid)?
.ok_or(Error::BadRequest(
ErrorKind::Forbidden,
"UIAA session does not exist.",
@@ -267,6 +185,18 @@ impl Uiaa {
)
.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)
}
}
+161 -144
View File
@@ -1,47 +1,52 @@
use crate::{utils, Error, Result};
use ruma::{
api::client::{error::ErrorKind, r0::device::Device},
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
api::client::{
error::ErrorKind,
r0::{
device::Device,
keys::{CrossSigningKey, OneTimeKey},
},
},
encryption::DeviceKeys,
events::{AnyToDeviceEvent, EventType},
identifiers::MxcUri,
serde::Raw,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, UInt, UserId,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UInt, UserId,
};
use std::{collections::BTreeMap, convert::TryFrom, mem, sync::Arc};
use super::abstraction::Tree;
use std::{collections::BTreeMap, convert::TryFrom, mem, time::SystemTime};
#[derive(Clone)]
pub struct Users {
pub(super) userid_password: Arc<dyn Tree>,
pub(super) userid_displayname: Arc<dyn Tree>,
pub(super) userid_avatarurl: Arc<dyn Tree>,
pub(super) userdeviceid_token: Arc<dyn Tree>,
pub(super) userdeviceid_metadata: Arc<dyn Tree>, // This is also used to check if a device exists
pub(super) userid_devicelistversion: Arc<dyn Tree>, // DevicelistVersion = u64
pub(super) token_userdeviceid: Arc<dyn Tree>,
pub(super) userid_password: sled::Tree,
pub(super) userid_displayname: sled::Tree,
pub(super) userid_avatarurl: sled::Tree,
pub(super) userdeviceid_token: sled::Tree,
pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists
pub(super) userid_devicelistversion: sled::Tree, // DevicelistVersion = u64
pub(super) token_userdeviceid: sled::Tree,
pub(super) onetimekeyid_onetimekeys: Arc<dyn Tree>, // OneTimeKeyId = UserId + DeviceKeyId
pub(super) userid_lastonetimekeyupdate: Arc<dyn Tree>, // LastOneTimeKeyUpdate = Count
pub(super) keychangeid_userid: Arc<dyn Tree>, // KeyChangeId = UserId/RoomId + Count
pub(super) keyid_key: Arc<dyn Tree>, // KeyId = UserId + KeyId (depends on key type)
pub(super) userid_masterkeyid: Arc<dyn Tree>,
pub(super) userid_selfsigningkeyid: Arc<dyn Tree>,
pub(super) userid_usersigningkeyid: Arc<dyn Tree>,
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + DeviceKeyId
pub(super) userid_lastonetimekeyupdate: sled::Tree, // LastOneTimeKeyUpdate = Count
pub(super) keychangeid_userid: sled::Tree, // KeyChangeId = UserId/RoomId + Count
pub(super) keyid_key: sled::Tree, // KeyId = UserId + KeyId (depends on key type)
pub(super) userid_masterkeyid: sled::Tree,
pub(super) userid_selfsigningkeyid: sled::Tree,
pub(super) userid_usersigningkeyid: sled::Tree,
pub(super) todeviceid_events: Arc<dyn Tree>, // ToDeviceId = UserId + DeviceId + Count
pub(super) todeviceid_events: sled::Tree, // ToDeviceId = UserId + DeviceId + Count
}
impl Users {
/// Check if a user has an account on this homeserver.
pub fn exists(&self, user_id: &UserId) -> Result<bool> {
Ok(self.userid_password.get(user_id.as_bytes())?.is_some())
Ok(self.userid_password.contains_key(user_id.to_string())?)
}
/// Check if account is deactivated
pub fn is_deactivated(&self, user_id: &UserId) -> Result<bool> {
Ok(self
.userid_password
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"User does not exist.",
@@ -50,20 +55,20 @@ impl Users {
}
/// Create a new user account on this homeserver.
pub fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
pub fn create(&self, user_id: &UserId, password: &str) -> Result<()> {
self.set_password(user_id, password)?;
Ok(())
}
/// Returns the number of users registered on this server.
pub fn count(&self) -> Result<usize> {
Ok(self.userid_password.iter().count())
pub fn count(&self) -> usize {
self.userid_password.iter().count()
}
/// Find out which user an access token belongs to.
pub fn find_from_token(&self, token: &str) -> Result<Option<(UserId, String)>> {
self.token_userdeviceid
.get(token.as_bytes())?
.get(token)?
.map_or(Ok(None), |bytes| {
let mut parts = bytes.split(|&b| b == 0xff);
let user_bytes = parts.next().ok_or_else(|| {
@@ -88,19 +93,21 @@ impl Users {
}
/// Returns an iterator over all users on this homeserver.
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> + '_ {
self.userid_password.iter().map(|(bytes, _)| {
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("User ID in userid_password is invalid unicode.")
})?)
.map_err(|_| Error::bad_database("User ID in userid_password is invalid."))
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> {
self.userid_password.iter().keys().map(|bytes| {
Ok(
UserId::try_from(utils::string_from_bytes(&bytes?).map_err(|_| {
Error::bad_database("User ID in userid_password is invalid unicode.")
})?)
.map_err(|_| Error::bad_database("User ID in userid_password is invalid."))?,
)
})
}
/// Returns the password hash for the given user.
pub fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> {
self.userid_password
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.map_or(Ok(None), |bytes| {
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Password hash in db is not valid string.")
@@ -109,28 +116,22 @@ impl Users {
}
/// Hash and set the user's password to the Argon2 hash
pub fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
if let Some(password) = password {
if let Ok(hash) = utils::calculate_hash(&password) {
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 {
self.userid_password.insert(user_id.as_bytes(), b"")?;
pub fn set_password(&self, user_id: &UserId, password: &str) -> Result<()> {
if let Ok(hash) = utils::calculate_hash(&password) {
self.userid_password.insert(user_id.to_string(), &*hash)?;
Ok(())
} else {
Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Password does not meet the requirements.",
))
}
}
/// Returns the displayname of a user on this homeserver.
pub fn displayname(&self, user_id: &UserId) -> Result<Option<String>> {
self.userid_displayname
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.map_or(Ok(None), |bytes| {
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Displayname in db is invalid.")
@@ -142,9 +143,9 @@ impl Users {
pub fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()> {
if let Some(displayname) = displayname {
self.userid_displayname
.insert(user_id.as_bytes(), displayname.as_bytes())?;
.insert(user_id.to_string(), &*displayname)?;
} else {
self.userid_displayname.remove(user_id.as_bytes())?;
self.userid_displayname.remove(user_id.to_string())?;
}
Ok(())
@@ -153,7 +154,7 @@ impl Users {
/// Get a the avatar_url of a user.
pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<MxcUri>> {
self.userid_avatarurl
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.map(|bytes| {
let s = utils::string_from_bytes(&bytes)
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
@@ -166,9 +167,9 @@ impl Users {
pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<MxcUri>) -> Result<()> {
if let Some(avatar_url) = avatar_url {
self.userid_avatarurl
.insert(user_id.as_bytes(), avatar_url.to_string().as_bytes())?;
.insert(user_id.to_string(), avatar_url.to_string().as_str())?;
} else {
self.userid_avatarurl.remove(user_id.as_bytes())?;
self.userid_avatarurl.remove(user_id.to_string())?;
}
Ok(())
@@ -190,17 +191,19 @@ impl Users {
userdeviceid.extend_from_slice(device_id.as_bytes());
self.userid_devicelistversion
.increment(user_id.as_bytes())?;
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.insert(
&userdeviceid,
&serde_json::to_vec(&Device {
userdeviceid,
serde_json::to_string(&Device {
device_id: device_id.into(),
display_name: initial_device_display_name,
last_seen_ip: None, // TODO
last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()),
last_seen_ts: Some(SystemTime::now()),
})
.expect("Device::to_string never fails."),
.expect("Device::to_string never fails.")
.as_bytes(),
)?;
self.set_token(user_id, &device_id, token)?;
@@ -215,8 +218,7 @@ impl Users {
userdeviceid.extend_from_slice(device_id.as_bytes());
// Remove tokens
if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? {
self.userdeviceid_token.remove(&userdeviceid)?;
if let Some(old_token) = self.userdeviceid_token.remove(&userdeviceid)? {
self.token_userdeviceid.remove(&old_token)?;
}
@@ -224,14 +226,15 @@ impl Users {
let mut prefix = userdeviceid.clone();
prefix.push(0xff);
for (key, _) in self.todeviceid_events.scan_prefix(prefix) {
self.todeviceid_events.remove(&key)?;
for key in self.todeviceid_events.scan_prefix(&prefix).keys() {
self.todeviceid_events.remove(key?)?;
}
// TODO: Remove onetimekeys
self.userid_devicelistversion
.increment(user_id.as_bytes())?;
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.remove(&userdeviceid)?;
@@ -239,18 +242,16 @@ impl Users {
}
/// Returns an iterator over all device ids of this user.
pub fn all_device_ids<'a>(
&'a self,
user_id: &UserId,
) -> impl Iterator<Item = Result<Box<DeviceId>>> + 'a {
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<Box<DeviceId>>> {
let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff);
// All devices have metadata
self.userdeviceid_metadata
.scan_prefix(prefix)
.map(|(bytes, _)| {
.keys()
.map(|bytes| {
Ok(utils::string_from_bytes(
&bytes
&*bytes?
.rsplit(|&b| b == 0xff)
.next()
.ok_or_else(|| Error::bad_database("UserDevice ID in db is invalid."))?,
@@ -271,15 +272,13 @@ impl Users {
// Remove old token
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
}
// Assign token to user device combination
self.userdeviceid_token
.insert(&userdeviceid, token.as_bytes())?;
self.token_userdeviceid
.insert(token.as_bytes(), &userdeviceid)?;
self.userdeviceid_token.insert(&userdeviceid, &*token)?;
self.token_userdeviceid.insert(token, userdeviceid)?;
Ok(())
}
@@ -311,7 +310,8 @@ impl Users {
self.onetimekeyid_onetimekeys.insert(
&key,
&serde_json::to_vec(&one_time_key_value).expect("OneTimeKey::to_vec always works"),
&*serde_json::to_string(&one_time_key_value)
.expect("OneTimeKey::to_string always works"),
)?;
self.userid_lastonetimekeyupdate
@@ -351,9 +351,10 @@ impl Users {
.insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
self.onetimekeyid_onetimekeys
.scan_prefix(prefix)
.scan_prefix(&prefix)
.next()
.map(|(key, value)| {
.map(|r| {
let (key, value) = r?;
self.onetimekeyid_onetimekeys.remove(&key)?;
Ok((
@@ -383,20 +384,21 @@ impl Users {
let mut counts = BTreeMap::new();
for algorithm in
self.onetimekeyid_onetimekeys
.scan_prefix(userdeviceid)
.map(|(bytes, _)| {
Ok::<_, Error>(
serde_json::from_slice::<DeviceKeyId>(
&*bytes.rsplit(|&b| b == 0xff).next().ok_or_else(|| {
Error::bad_database("OneTimeKey ID in db is invalid.")
})?,
)
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))?
.algorithm(),
for algorithm in self
.onetimekeyid_onetimekeys
.scan_prefix(&userdeviceid)
.keys()
.map(|bytes| {
Ok::<_, Error>(
serde_json::from_slice::<DeviceKeyId>(
&*bytes?.rsplit(|&b| b == 0xff).next().ok_or_else(|| {
Error::bad_database("OneTimeKey ID in db is invalid.")
})?,
)
})
.map_err(|_| Error::bad_database("DeviceKeyId in db is invalid."))?
.algorithm(),
)
})
{
*counts.entry(algorithm?).or_default() += UInt::from(1_u32);
}
@@ -418,7 +420,7 @@ impl Users {
self.keyid_key.insert(
&userdeviceid,
&serde_json::to_vec(&device_keys).expect("DeviceKeys::to_vec always works"),
&*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"),
)?;
self.mark_device_key_update(user_id, rooms, globals)?;
@@ -459,11 +461,11 @@ impl Users {
self.keyid_key.insert(
&master_key_key,
&serde_json::to_vec(&master_key).expect("CrossSigningKey::to_vec always works"),
&*serde_json::to_string(&master_key).expect("CrossSigningKey::to_string always works"),
)?;
self.userid_masterkeyid
.insert(user_id.as_bytes(), &master_key_key)?;
.insert(&*user_id.to_string(), master_key_key)?;
// Self-signing key
if let Some(self_signing_key) = self_signing_key {
@@ -485,12 +487,12 @@ impl Users {
self.keyid_key.insert(
&self_signing_key_key,
&serde_json::to_vec(&self_signing_key)
.expect("CrossSigningKey::to_vec always works"),
&*serde_json::to_string(&self_signing_key)
.expect("CrossSigningKey::to_string always works"),
)?;
self.userid_selfsigningkeyid
.insert(user_id.as_bytes(), &self_signing_key_key)?;
.insert(&*user_id.to_string(), self_signing_key_key)?;
}
// User-signing key
@@ -513,12 +515,12 @@ impl Users {
self.keyid_key.insert(
&user_signing_key_key,
&serde_json::to_vec(&user_signing_key)
.expect("CrossSigningKey::to_vec always works"),
&*serde_json::to_string(&user_signing_key)
.expect("CrossSigningKey::to_string always works"),
)?;
self.userid_usersigningkeyid
.insert(user_id.as_bytes(), &user_signing_key_key)?;
.insert(&*user_id.to_string(), user_signing_key_key)?;
}
self.mark_device_key_update(user_id, rooms, globals)?;
@@ -560,7 +562,8 @@ impl Users {
self.keyid_key.insert(
&key,
&serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
&*serde_json::to_string(&cross_signing_key)
.expect("CrossSigningKey::to_string always works"),
)?;
// TODO: Should we notify about this change?
@@ -570,26 +573,36 @@ impl Users {
}
#[tracing::instrument(skip(self))]
pub fn keys_changed<'a>(
&'a self,
pub fn keys_changed(
&self,
user_or_room_id: &str,
from: u64,
to: Option<u64>,
) -> impl Iterator<Item = Result<UserId>> + 'a {
) -> impl Iterator<Item = Result<UserId>> {
let mut prefix = user_or_room_id.as_bytes().to_vec();
prefix.push(0xff);
let mut start = prefix.clone();
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
.iter_from(&start, false)
.range(start..end)
.filter_map(|r| r.ok())
.take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(_, bytes)| {
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database("User ID in devicekeychangeid_userid is invalid unicode.")
})?)
.map_err(|_| Error::bad_database("User ID in devicekeychangeid_userid is invalid."))
Ok(
UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
Error::bad_database(
"User ID in devicekeychangeid_userid is invalid unicode.",
)
})?)
.map_err(|_| {
Error::bad_database("User ID in devicekeychangeid_userid is invalid.")
})?,
)
})
}
@@ -613,13 +626,13 @@ impl Users {
key.push(0xff);
key.extend_from_slice(&count);
self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
}
let mut key = user_id.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(&count);
self.keychangeid_userid.insert(&key, user_id.as_bytes())?;
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
Ok(())
}
@@ -633,22 +646,23 @@ impl Users {
key.push(0xff);
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(|_| {
Error::bad_database("DeviceKeys in db are invalid.")
})?))
})
}
pub fn get_master_key<F: Fn(&UserId) -> bool>(
pub fn get_master_key(
&self,
user_id: &UserId,
allowed_signatures: F,
sender_id: &UserId,
) -> Result<Option<CrossSigningKey>> {
// TODO: hide some signatures
self.userid_masterkeyid
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.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)
.map_err(|_| {
Error::bad_database("CrossSigningKey in db is invalid.")
@@ -659,7 +673,7 @@ impl Users {
cross_signing_key.signatures = cross_signing_key
.signatures
.into_iter()
.filter(|(user, _)| allowed_signatures(user))
.filter(|(user, _)| user == user_id || user == sender_id)
.collect();
Ok(Some(cross_signing_key))
@@ -667,15 +681,15 @@ impl Users {
})
}
pub fn get_self_signing_key<F: Fn(&UserId) -> bool>(
pub fn get_self_signing_key(
&self,
user_id: &UserId,
allowed_signatures: F,
sender_id: &UserId,
) -> Result<Option<CrossSigningKey>> {
self.userid_selfsigningkeyid
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.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)
.map_err(|_| {
Error::bad_database("CrossSigningKey in db is invalid.")
@@ -686,7 +700,7 @@ impl Users {
cross_signing_key.signatures = cross_signing_key
.signatures
.into_iter()
.filter(|(user, _)| user == user_id || allowed_signatures(user))
.filter(|(user, _)| user == user_id || user == sender_id)
.collect();
Ok(Some(cross_signing_key))
@@ -696,9 +710,9 @@ impl Users {
pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<CrossSigningKey>> {
self.userid_usersigningkeyid
.get(user_id.as_bytes())?
.get(user_id.to_string())?
.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(|_| {
Error::bad_database("CrossSigningKey in db is invalid.")
})?))
@@ -711,7 +725,7 @@ impl Users {
sender: &UserId,
target_user_id: &UserId,
target_device_id: &DeviceId,
event_type: &str,
event_type: &EventType,
content: serde_json::Value,
globals: &super::globals::Globals,
) -> Result<()> {
@@ -722,13 +736,13 @@ impl Users {
key.extend_from_slice(&globals.next_count()?.to_be_bytes());
let mut json = serde_json::Map::new();
json.insert("type".to_owned(), event_type.to_owned().into());
json.insert("type".to_owned(), event_type.to_string().into());
json.insert("sender".to_owned(), sender.to_string().into());
json.insert("content".to_owned(), content);
self.todeviceid_events.insert(
&key,
&serde_json::to_vec(&json).expect("Map::to_vec always works"),
&*serde_json::to_string(&json).expect("Map::to_string always works"),
)?;
Ok(())
@@ -747,9 +761,9 @@ impl Users {
prefix.extend_from_slice(device_id.as_bytes());
prefix.push(0xff);
for (_, value) in self.todeviceid_events.scan_prefix(prefix) {
for value in self.todeviceid_events.scan_prefix(&prefix).values() {
events.push(
serde_json::from_slice(&value)
serde_json::from_slice(&*value?)
.map_err(|_| Error::bad_database("Event in todeviceid_events is invalid."))?,
);
}
@@ -774,9 +788,10 @@ impl Users {
for (key, _) in self
.todeviceid_events
.iter_from(&last, true)
.take_while(move |(k, _)| k.starts_with(&prefix))
.map(|(key, _)| {
.range(&*prefix..=&*last)
.keys()
.map(|key| {
let key = key?;
Ok::<_, Error>((
key.clone(),
utils::u64_from_bytes(&key[key.len() - mem::size_of::<u64>()..key.len()])
@@ -786,7 +801,7 @@ impl Users {
.filter_map(|r| r.ok())
.take_while(|&(_, count)| count <= until)
{
self.todeviceid_events.remove(&key)?;
self.todeviceid_events.remove(key)?;
}
Ok(())
@@ -806,11 +821,14 @@ impl Users {
assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some());
self.userid_devicelistversion
.increment(user_id.as_bytes())?;
.update_and_fetch(&user_id.as_bytes(), utils::increment)?
.expect("utils::increment will always put in a value");
self.userdeviceid_metadata.insert(
&userdeviceid,
&serde_json::to_vec(device).expect("Device::to_string always works"),
userdeviceid,
serde_json::to_string(device)
.expect("Device::to_string always works")
.as_bytes(),
)?;
Ok(())
@@ -845,18 +863,17 @@ impl Users {
})
}
pub fn all_devices_metadata<'a>(
&'a self,
user_id: &UserId,
) -> impl Iterator<Item = Result<Device>> + 'a {
pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> {
let mut key = user_id.as_bytes().to_vec();
key.push(0xff);
self.userdeviceid_metadata
.scan_prefix(key)
.map(|(_, bytes)| {
serde_json::from_slice::<Device>(&bytes)
.map_err(|_| Error::bad_database("Device in userdeviceid_metadata is invalid."))
.values()
.map(|bytes| {
Ok(serde_json::from_slice::<Device>(&bytes?).map_err(|_| {
Error::bad_database("Device in userdeviceid_metadata is invalid.")
})?)
})
}
@@ -870,7 +887,7 @@ impl Users {
// 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
// password without logging in should check if the account is deactivated.
self.userid_password.insert(user_id.as_bytes(), &[])?;
self.userid_password.insert(user_id.to_string(), "")?;
// TODO: Unhook 3PID
Ok(())
+16 -48
View File
@@ -1,11 +1,5 @@
use log::{error, warn};
use ruma::{
api::client::{
error::{Error as RumaError, ErrorKind},
r0::uiaa::UiaaInfo,
},
ServerName,
};
use log::error;
use ruma::api::client::{error::ErrorKind, r0::uiaa::UiaaInfo};
use thiserror::Error;
#[cfg(feature = "conduit_bin")]
@@ -16,25 +10,18 @@ use {
response::{self, Responder},
Request,
},
ruma::api::client::r0::uiaa::UiaaResponse,
ruma::api::client::{error::Error as RumaError, r0::uiaa::UiaaResponse},
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[cfg(feature = "sled")]
#[error("There was a problem with the connection to the sled database.")]
#[error("There was a problem with the connection to the database.")]
SledError {
#[from]
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.")]
ImageError {
#[from]
@@ -46,13 +33,6 @@ pub enum Error {
source: reqwest::Error,
},
#[error("{0}")]
FederationError(Box<ServerName>, RumaError),
#[error("Could not do this io: {source}")]
IoError {
#[from]
source: std::io::Error,
},
#[error("{0}")]
BadServerResponse(&'static str),
#[error("{0}")]
BadConfig(&'static str),
@@ -61,6 +41,7 @@ pub enum Error {
BadDatabase(&'static str),
#[error("uiaa")]
Uiaa(UiaaInfo),
#[error("{0}: {1}")]
BadRequest(ErrorKind, &'static str),
#[error("{0}")]
@@ -79,16 +60,14 @@ impl Error {
}
}
impl Error {
pub fn to_response(&self) -> RumaResponse<UiaaResponse> {
if let Self::Uiaa(uiaainfo) = self {
return RumaResponse(UiaaResponse::AuthResponse(uiaainfo.clone()));
}
if let Self::FederationError(origin, error) = self {
let mut error = error.clone();
error.message = format!("Answer from {}: {}", origin, error.message);
return RumaResponse(UiaaResponse::MatrixError(error));
#[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> {
if let Self::Uiaa(uiaainfo) = &self {
return RumaResponse::from(UiaaResponse::AuthResponse(uiaainfo.clone())).respond_to(r);
}
let message = format!("{}", self);
@@ -113,22 +92,11 @@ impl Error {
_ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR),
};
warn!("{}: {}", status_code, message);
RumaResponse(UiaaResponse::MatrixError(RumaError {
RumaResponse::from(RumaError {
kind,
message,
status_code,
}))
}
}
#[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)
})
.respond_to(r)
}
}
-2
View File
@@ -1,6 +1,4 @@
#![allow(clippy::suspicious_else_formatting)]
#![deny(clippy::dbg_macro)]
pub mod appservice_server;
pub mod client_server;
mod database;
+8 -17
View File
@@ -1,6 +1,5 @@
#![warn(rust_2018_idioms)]
#![allow(clippy::suspicious_else_formatting)]
#![deny(clippy::dbg_macro)]
pub mod appservice_server;
pub mod client_server;
@@ -12,8 +11,6 @@ mod pdu;
mod ruma_wrapper;
mod utils;
use std::sync::Arc;
use database::Config;
pub use database::Database;
pub use error::{Error, Result};
@@ -33,7 +30,7 @@ use rocket::{
use tracing::span;
use tracing_subscriber::{prelude::*, Registry};
fn setup_rocket(config: Figment, data: Arc<Database>) -> rocket::Rocket<rocket::Build> {
fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build> {
rocket::custom(config)
.manage(data)
.mount(
@@ -71,7 +68,6 @@ fn setup_rocket(config: Figment, data: Arc<Database>) -> rocket::Rocket<rocket::
client_server::get_avatar_url_route,
client_server::get_profile_route,
client_server::set_presence_route,
client_server::get_presence_route,
client_server::upload_keys_route,
client_server::get_keys_route,
client_server::claim_keys_route,
@@ -153,8 +149,6 @@ fn setup_rocket(config: Figment, data: Arc<Database>) -> rocket::Rocket<rocket::
server_server::send_transaction_message_route,
server_server::get_event_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::create_join_event_template_route,
server_server::create_join_event_route,
@@ -162,8 +156,6 @@ fn setup_rocket(config: Figment, data: Arc<Database>) -> rocket::Rocket<rocket::
server_server::get_devices_route,
server_server::get_room_information_route,
server_server::get_profile_information_route,
server_server::get_keys_route,
server_server::claim_keys_route,
],
)
.register(
@@ -201,6 +193,8 @@ async fn main() {
.await
.expect("config is valid");
db.sending.start_handler(&db);
if config.allow_jaeger {
let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline()
.with_service_name("conduit")
@@ -211,16 +205,13 @@ async fn main() {
let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
let _enter = root.enter();
let rocket = setup_rocket(raw_config, db);
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();
std::env::set_var("CONDUIT_LOG", config.log);
pretty_env_logger::init_custom_env("CONDUIT_LOG");
}
let rocket = setup_rocket(raw_config, db);
rocket.launch().await.unwrap();
}
#[catch(404)]
+12 -14
View File
@@ -2,17 +2,15 @@ use crate::Error;
use log::error;
use ruma::{
events::{
pdu::EventHash, room::member::MemberEventContent, AnyEphemeralRoomEvent, AnyRoomEvent,
AnyStateEvent, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType,
StateEvent,
pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent,
AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, StateEvent,
},
serde::{CanonicalJsonObject, CanonicalJsonValue, Raw},
state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName,
ServerSigningKeyId, UInt, UserId,
EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom};
use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH};
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct PduEvent {
@@ -74,7 +72,9 @@ impl PduEvent {
self.unsigned.insert(
"redacted_because".to_owned(),
serde_json::to_value(reason).expect("to_value(PduEvent) always works"),
serde_json::to_string(reason)
.expect("PduEvent::to_string always works")
.into(),
);
self.content = new_content.into();
@@ -105,7 +105,7 @@ impl PduEvent {
/// This only works for events that are also AnyRoomEvents.
#[tracing::instrument(skip(self))]
pub fn to_any_event(&self) -> Raw<AnyEphemeralRoomEvent> {
pub fn to_any_event(&self) -> Raw<AnyEvent> {
let mut json = json!({
"content": self.content,
"type": self.kind,
@@ -213,10 +213,7 @@ impl PduEvent {
pub fn convert_to_outgoing_federation_event(
mut pdu_json: CanonicalJsonObject,
) -> Raw<ruma::events::pdu::Pdu> {
if let Some(unsigned) = pdu_json
.get_mut("unsigned")
.and_then(|val| val.as_object_mut())
{
if let Some(CanonicalJsonValue::Object(unsigned)) = pdu_json.get_mut("unsigned") {
unsigned.remove("transaction_id");
}
@@ -267,9 +264,10 @@ impl state_res::Event for PduEvent {
fn content(&self) -> serde_json::Value {
self.content.clone()
}
fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
MilliSecondsSinceUnixEpoch(self.origin_server_ts)
fn origin_server_ts(&self) -> std::time::SystemTime {
UNIX_EPOCH + std::time::Duration::from_millis(self.origin_server_ts.into())
}
fn state_key(&self) -> Option<String> {
self.state_key.clone()
}
+84 -127
View File
@@ -1,15 +1,14 @@
use crate::Error;
use ruma::{
api::{client::r0::uiaa::UiaaResponse, OutgoingResponse},
api::OutgoingResponse,
identifiers::{DeviceId, UserId},
signatures::CanonicalJsonValue,
Outgoing, ServerName,
Outgoing,
};
use std::ops::Deref;
#[cfg(feature = "conduit_bin")]
use {
crate::{server_server, Database},
crate::{server_server, utils},
log::{debug, warn},
rocket::{
data::{self, ByteUnit, Data, FromData},
@@ -19,11 +18,14 @@ use {
tokio::io::AsyncReadExt,
Request, State,
},
ruma::api::{AuthScheme, IncomingRequest},
ruma::{
api::{AuthScheme, IncomingRequest},
signatures::CanonicalJsonValue,
ServerName,
},
std::collections::BTreeMap,
std::convert::TryFrom,
std::io::Cursor,
std::sync::Arc,
};
/// This struct converts rocket requests into ruma structs by converting them into http requests
@@ -32,9 +34,8 @@ pub struct Ruma<T: Outgoing> {
pub body: T::Incoming,
pub sender_user: Option<UserId>,
pub sender_device: Option<Box<DeviceId>>,
pub sender_servername: Option<Box<ServerName>>,
// This is None when body is not a valid string
pub json_body: Option<CanonicalJsonValue>,
pub json_body: Option<Box<serde_json::value::RawValue>>,
pub from_appservice: bool,
}
@@ -49,7 +50,7 @@ where
async fn from_data(request: &'a Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
let metadata = T::Incoming::METADATA;
let db = request
.guard::<State<'_, Arc<Database>>>()
.guard::<State<'_, crate::Database>>()
.await
.expect("database was loaded");
@@ -57,7 +58,7 @@ where
let token = request
.headers()
.get_one("Authorization")
.and_then(|s| s.get(7..)) // Split off "Bearer "
.map(|s| s[7..].to_owned()) // Split off "Bearer "
.or_else(|| request.query_value("access_token").and_then(|r| r.ok()));
let limit = db.globals.max_request_size();
@@ -65,15 +66,9 @@ where
let mut body = Vec::new();
handle.read_to_end(&mut body).await.unwrap();
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
let (sender_user, sender_device, from_appservice) = if let Some((_id, registration)) = db
.appservice
.iter_all()
.unwrap()
.filter_map(|r| r.ok())
.find(|(_id, registration)| {
registration
@@ -107,10 +102,10 @@ where
}
// TODO: Check if appservice is allowed to be that user
(Some(user_id), None, None, true)
(Some(user_id), None, true)
}
AuthScheme::ServerSignatures => (None, None, None, true),
AuthScheme::None => (None, None, None, true),
AuthScheme::ServerSignatures => (None, None, true),
AuthScheme::None => (None, None, true),
}
} else {
match metadata.authentication {
@@ -119,12 +114,9 @@ where
match db.users.find_from_token(&token).unwrap() {
// Unknown Token
None => return Failure((Status::raw(581), ())),
Some((user_id, device_id)) => (
Some(user_id),
Some(Box::<DeviceId>::from(device_id)),
None,
false,
),
Some((user_id, device_id)) => {
(Some(user_id), Some(device_id.into()), false)
}
}
} else {
// Missing Token
@@ -133,18 +125,16 @@ where
}
AuthScheme::ServerSignatures => {
// Get origin from header
let x_matrix = match request
.headers()
.get_one("Authorization")
.and_then(|s| s.get(9..)) // Split off "X-Matrix " and parse the rest
.map(|s| {
s.split_terminator(',')
.map(|field| {
let mut splits = field.splitn(2, '=');
(splits.next(), splits.next().map(|s| s.trim_matches('"')))
})
.collect::<BTreeMap<_, _>>()
}) {
let x_matrix = match request.headers().get_one("Authorization").map(|s| {
// Split off "X-Matrix " and parse the rest
s[9..]
.split_terminator(',')
.map(|field| {
let mut splits = field.splitn(2, '=');
(splits.next(), splits.next().map(|s| s.trim_matches('"')))
})
.collect::<BTreeMap<_, _>>()
}) {
Some(t) => t,
None => {
warn!("No Authorization header");
@@ -197,10 +187,12 @@ where
}
};
let json_body = serde_json::from_slice::<CanonicalJsonValue>(&body);
let mut request_map = BTreeMap::<String, CanonicalJsonValue>::new();
if let Some(json_body) = &json_body {
request_map.insert("content".to_owned(), json_body.clone());
if let Ok(json_body) = json_body {
request_map.insert("content".to_owned(), json_body);
};
request_map.insert(
@@ -235,24 +227,27 @@ where
CanonicalJsonValue::Object(signatures),
);
let keys =
match server_server::fetch_signing_keys(&db, &origin, vec![key.to_owned()])
.await
{
Ok(b) => b,
Err(e) => {
warn!("Failed to fetch signing keys: {}", e);
let keys = match server_server::fetch_signing_keys(
&db,
&origin,
vec![&key.to_owned()],
)
.await
{
Ok(b) => b,
Err(e) => {
warn!("Failed to fetch signing keys: {}", e);
// Forbidden
return Failure((Status::raw(580), ()));
}
};
// Forbidden
return Failure((Status::raw(580), ()));
}
};
let mut pub_key_map = BTreeMap::new();
pub_key_map.insert(origin.as_str().to_owned(), keys);
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
Ok(()) => (None, None, Some(origin), false),
Ok(()) => (None, None, false),
Err(e) => {
warn!("Failed to verify json request from {}: {}", origin, e);
@@ -265,7 +260,7 @@ where
}
}
}
AuthScheme::None => (None, None, None, false),
AuthScheme::None => (None, None, false),
}
};
@@ -276,35 +271,6 @@ where
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();
debug!("{:?}", http_request);
match <T::Incoming as IncomingRequest>::try_from_http_request(http_request) {
@@ -312,9 +278,11 @@ where
body: t,
sender_user,
sender_device,
sender_servername,
// TODO: Can we avoid parsing it again? (We only need this for append_pdu)
json_body: utils::string_from_bytes(&body)
.ok()
.and_then(|s| serde_json::value::RawValue::from_string(s).ok()),
from_appservice,
json_body,
}),
Err(e) => {
warn!("{:?}", e);
@@ -335,60 +303,49 @@ impl<T: Outgoing> Deref for Ruma<T> {
/// This struct converts ruma responses into rocket http responses.
pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>;
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)?;
pub struct RumaResponse<T: OutgoingResponse>(pub 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> {
impl<T: OutgoingResponse> From<T> for RumaResponse<T> {
fn from(t: T) -> Self {
Self(t)
}
}
impl From<Error> for RumaResponse<UiaaResponse> {
fn from(t: Error) -> Self {
t.to_response()
}
}
#[cfg(feature = "conduit_bin")]
impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T>
where
T: Send + OutgoingResponse,
'o: 'r,
T: OutgoingResponse,
{
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
response(self)
let http_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()
}
}
+200 -569
View File
File diff suppressed because it is too large Load Diff
+24 -28
View File
@@ -15,15 +15,6 @@ pub fn millis_since_unix_epoch() -> 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>> {
let number = match old.map(|bytes| bytes.try_into()) {
Some(Ok(bytes)) => {
@@ -36,14 +27,16 @@ pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
Some(number.to_be_bytes().to_vec())
}
pub fn generate_keypair() -> Vec<u8> {
let mut value = random_string(8).as_bytes().to_vec();
value.push(0xff);
value.extend_from_slice(
&ruma::signatures::Ed25519KeyPair::generate()
.expect("Ed25519KeyPair generation always works (?)"),
);
value
pub fn generate_keypair(old: Option<&[u8]>) -> Option<Vec<u8>> {
Some(old.map(|s| s.to_vec()).unwrap_or_else(|| {
let mut value = random_string(8).as_bytes().to_vec();
value.push(0xff);
value.extend_from_slice(
&ruma::signatures::Ed25519KeyPair::generate()
.expect("Ed25519KeyPair generation always works (?)"),
);
value
}))
}
/// Parses the bytes into an u64.
@@ -84,19 +77,22 @@ pub fn common_elements(
let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>();
Some(first_iterator.filter(move |target| {
other_iterators.iter_mut().all(|it| {
while let Some(element) = it.peek() {
match check_order(element, target) {
Ordering::Greater => return false, // We went too far
Ordering::Equal => return true, // Element is in both iters
Ordering::Less => {
// Keep searching
it.next();
other_iterators
.iter_mut()
.map(|it| {
while let Some(element) = it.peek() {
match check_order(element, target) {
Ordering::Greater => return false, // We went too far
Ordering::Equal => return true, // Element is in both iters
Ordering::Less => {
// Keep searching
it.next();
}
}
}
}
false
})
false
})
.all(|b| b)
}))
}