mirror of
https://gitlab.com/famedly/conduit.git
synced 2024-11-26 19:22:29 +00:00
Merge branch 'correct-sendtxn' into pushers
This commit is contained in:
commit
21f785d530
15
.gitlab/issue_templates/Issue Template.md
Normal file
15
.gitlab/issue_templates/Issue Template.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Headline
|
||||
|
||||
### Description
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/label ~conduit
|
762
Cargo.lock
generated
762
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
68
Cargo.toml
68
Cargo.toml
@ -4,7 +4,7 @@ description = "A Matrix homeserver written in Rust"
|
||||
license = "Apache-2.0"
|
||||
authors = ["timokoesters <timo@koesters.xyz>"]
|
||||
homepage = "https://conduit.rs"
|
||||
repository = "https://git.koesters.xyz/timo/conduit"
|
||||
repository = "https://gitlab.com/famedly/conduit"
|
||||
readme = "README.md"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
@ -14,55 +14,64 @@ edition = "2018"
|
||||
[dependencies]
|
||||
# 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 = "c24f15c18f02319be83af4f3c1951dc220b52c5e", features = ["tls"] } # Used to handle requests
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "93e62c86eddf7cc9a7fc40b044182f83f0d7d92a", 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", features = ["rand", "appservice-api", "client-api", "federation-api", "push-gateway-api", "unstable-pre-spec", "unstable-synapse-quirks", "unstable-exhaustive-types"], rev = "bba442580d6cd7ed990b2b63387eed2238cbadc8" }
|
||||
#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "appservice-api", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks", "unstable-exhaustive-types"], rev = "0a10afe6dacc2b7a50a8002c953d10b7fb4e37bc" }
|
||||
# ruma = { git = "https://github.com/DevinR528/ruma", features = ["rand", "client-api", "federation-api", "unstable-exhaustive-types", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "verified-export" }
|
||||
# ruma = { path = "../ruma/ruma", features = ["unstable-exhaustive-types", "rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
|
||||
ruma = { path = "../ruma/ruma", features = ["unstable-exhaustive-types", "rand", "client-api", "federation-api", "push-gateway-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
|
||||
|
||||
# Used when doing state resolution
|
||||
# state-res = { git = "https://github.com/timokoesters/state-res", branch = "timo-spec-comp", features = ["unstable-pre-spec"] }
|
||||
# TODO: remove the gen-eventid feature
|
||||
state-res = { git = "https://github.com/ruma/state-res", rev = "791c66d73cf064d09db0cdf767d5fef43a343425", features = ["unstable-pre-spec", "gen-eventid"] }
|
||||
# state-res = { path = "../../state-res", features = ["unstable-pre-spec", "gen-eventid"] }
|
||||
#state-res = { git = "https://github.com/ruma/state-res", branch = "main", features = ["unstable-pre-spec", "gen-eventid"] }
|
||||
# state-res = { git = "https://github.com/ruma/state-res", rev = "791c66d73cf064d09db0cdf767d5fef43a343425", features = ["unstable-pre-spec", "gen-eventid"] }
|
||||
state-res = { path = "../state-res", features = ["unstable-pre-spec", "gen-eventid"] }
|
||||
|
||||
# Used for long polling and federation sender, should be the same as rocket::tokio
|
||||
tokio = { version = "1.1.0", features = ["macros", "time", "sync"] }
|
||||
tokio = "1.2.0"
|
||||
# Used for storing data permanently
|
||||
sled = { version = "0.34.6", default-features = false }
|
||||
# Used for emitting log entries
|
||||
log = "0.4.11"
|
||||
log = "0.4.14"
|
||||
# Used for rocket<->ruma conversions
|
||||
http = "0.2.3"
|
||||
# Used to find data directory for default db path
|
||||
directories = "3.0.1"
|
||||
|
||||
# Used for ruma wrapper
|
||||
serde_json = { version = "1.0.60", features = ["raw_value"] }
|
||||
serde_json = { version = "1.0.64", features = ["raw_value"] }
|
||||
# Used for appservice registration files
|
||||
serde_yaml = "0.8.14"
|
||||
serde_yaml = "0.8.17"
|
||||
# Used for pdu definition
|
||||
serde = "1.0.117"
|
||||
serde = "1.0.123"
|
||||
# Used for secure identifiers
|
||||
rand = "0.7.3"
|
||||
rand = "0.8.3"
|
||||
# Used to hash passwords
|
||||
rust-argon2 = "0.8.3"
|
||||
# Used to send requests
|
||||
reqwest = "0.11.0"
|
||||
reqwest = { version = "0.11.1" }
|
||||
# Used for conduit::Error type
|
||||
thiserror = "1.0.22"
|
||||
thiserror = "1.0.24"
|
||||
# Used to generate thumbnails for images
|
||||
image = { version = "0.23.12", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
image = { version = "0.23.14", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
# Used to encode server public key
|
||||
base64 = "0.13.0"
|
||||
# Used when hashing the state
|
||||
ring = "0.16.19"
|
||||
ring = "0.16.20"
|
||||
# Used when querying the SRV record of other servers
|
||||
trust-dns-resolver = "0.20.0"
|
||||
# Used to find matching events for appservices
|
||||
regex = "1.4.2"
|
||||
regex = "1.4.3"
|
||||
# jwt jsonwebtokens
|
||||
jsonwebtoken = "7.2.0"
|
||||
# Performance measurements
|
||||
tracing = "0.1.25"
|
||||
opentelemetry = "0.12.0"
|
||||
tracing-subscriber = "0.2.16"
|
||||
tracing-opentelemetry = "0.11.0"
|
||||
opentelemetry-jaeger = "0.11.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
[features]
|
||||
default = ["conduit_bin"]
|
||||
@ -77,3 +86,26 @@ required-features = ["conduit_bin"]
|
||||
[lib]
|
||||
name = "conduit"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.deb]
|
||||
name = "matrix-conduit"
|
||||
maintainer = "Paul van Tilburg <paul@luon.net>"
|
||||
copyright = "2020, Timo Kösters <timo@koesters.xyz>"
|
||||
license-file = ["LICENSE", "3"]
|
||||
depends = "$auto, ca-certificates"
|
||||
extended-description = """\
|
||||
A fast Matrix homeserver that is optimized for smaller, personal servers, \
|
||||
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/local"
|
||||
]
|
||||
maintainer-scripts = "debian/"
|
||||
systemd-units = { unit-name = "matrix-conduit" }
|
||||
|
@ -30,6 +30,9 @@ max_request_size = 20_000_000 # in bytes
|
||||
#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 = 4 # How many requests Conduit sends to other servers at the same time
|
||||
#workers = 4 # default: cpu core count * 2
|
||||
|
29
debian/README.Debian
vendored
Normal file
29
debian/README.Debian
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
Conduit for Debian
|
||||
==================
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
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/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 files mentioned above to set up the
|
||||
environment before running the server.
|
||||
|
||||
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
|
||||
renewal, for it to work properly.
|
17
debian/config
vendored
Normal file
17
debian/config
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# Ask for the Matrix homeserver name, address and port.
|
||||
db_input high matrix-conduit/hostname || true
|
||||
db_go
|
||||
|
||||
db_input low matrix-conduit/address || true
|
||||
db_go
|
||||
|
||||
db_input medium matrix-conduit/port || true
|
||||
db_go
|
||||
|
||||
exit 0
|
33
debian/env.local
vendored
Normal file
33
debian/env.local
vendored
Normal 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"
|
49
debian/matrix-conduit.service
vendored
Normal file
49
debian/matrix-conduit.service
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
[Unit]
|
||||
Description=Conduit Matrix homeserver
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=_matrix-conduit
|
||||
Group=_matrix-conduit
|
||||
Type=simple
|
||||
|
||||
AmbientCapabilities=
|
||||
CapabilityBoundingSet=
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=strict
|
||||
PrivateDevices=yes
|
||||
PrivateMounts=yes
|
||||
PrivateTmp=yes
|
||||
PrivateUsers=yes
|
||||
RemoveIPC=yes
|
||||
RestrictAddressFamilies=AF_INET AF_INET6
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
StateDirectory=matrix-conduit
|
||||
|
||||
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
|
||||
RestartSec=10
|
||||
StartLimitInterval=1m
|
||||
StartLimitBurst=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
73
debian/postinst
vendored
Normal file
73
debian/postinst
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
|
||||
CONDUIT_CONFIG_FILE="$CONDUIT_CONFIG_PATH/debian"
|
||||
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
# Create the `_matrix-conduit` user if it does not exist yet.
|
||||
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 \
|
||||
--disabled-login \
|
||||
--force-badname \
|
||||
_matrix-conduit
|
||||
fi
|
||||
|
||||
# Create the database path if it does not exist yet.
|
||||
if [ ! -d "$CONDUIT_DATABASE_PATH" ]; then
|
||||
mkdir -p "$CONDUIT_DATABASE_PATH"
|
||||
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
||||
fi
|
||||
|
||||
# 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 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 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.
|
||||
ROCKET_ADDRESS="$ROCKET_ADDRESS"
|
||||
|
||||
# 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"
|
||||
|
||||
# THIS FILE IS GENERATED BY DEBCONF AND WILL BE OVERRIDDEN!
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
22
debian/postrm
vendored
Normal file
22
debian/postrm
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
|
||||
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
|
||||
|
||||
case $1 in
|
||||
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."
|
||||
if [ -d "$CONDUIT_CONFIG_PATH" ]; then
|
||||
rm -r "$CONDUIT_CONFIG_PATH"
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUIT_DATABASE_PATH" ]; then
|
||||
rm -r "$CONDUIT_DATABASE_PATH"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
21
debian/templates
vendored
Normal file
21
debian/templates
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Template: matrix-conduit/hostname
|
||||
Type: string
|
||||
Default: localhost
|
||||
Description: The server (host)name of the Matrix homeserver
|
||||
This is the hostname the homeserver will be reachable at via a client.
|
||||
.
|
||||
If set to "localhost", you can connect with a client locally and clients
|
||||
from other hosts and also other homeservers will not be able to reach you!
|
||||
|
||||
Template: matrix-conduit/address
|
||||
Type: string
|
||||
Default: 127.0.0.1
|
||||
Description: The listen address of the Matrix homeserver
|
||||
This is the address the homeserver will listen on. Leave it set to 127.0.0.1
|
||||
when using a reverse proxy.
|
||||
|
||||
Template: matrix-conduit/port
|
||||
Type: string
|
||||
Default: 14004
|
||||
Description: The port of the Matrix homeserver
|
||||
This port is most often just accessed by a reverse proxy.
|
@ -1 +1 @@
|
||||
merge_imports = true
|
||||
imports_granularity="Crate"
|
||||
|
@ -40,6 +40,7 @@ const GUEST_NAME_LENGTH: usize = 10;
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/register/available", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_register_available_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_username_availability::Request<'_>>,
|
||||
@ -82,6 +83,7 @@ pub async fn get_register_available_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/register", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn register_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<register::Request<'_>>,
|
||||
@ -453,16 +455,9 @@ pub async fn register_route(
|
||||
db.rooms.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: EventType::RoomMessage,
|
||||
content: serde_json::to_value(message::MessageEventContent::Text(
|
||||
message::TextMessageEventContent {
|
||||
body: "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing `/join #conduit:matrix.org`. **Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.** Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
|
||||
formatted: Some(message::FormattedBody {
|
||||
format: message::MessageFormat::Html,
|
||||
body: "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing <code>/join #conduit:matrix.org</code>. <strong>Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.</strong> Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
|
||||
}),
|
||||
relates_to: None,
|
||||
new_content: None,
|
||||
},
|
||||
content: serde_json::to_value(message::MessageEventContent::text_html(
|
||||
"Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing `/join #conduit:matrix.org`. **Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.** Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
|
||||
"Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing <code>/join #conduit:matrix.org</code>. <strong>Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.</strong> Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
@ -498,6 +493,7 @@ pub async fn register_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/account/password", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn change_password_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<change_password::Request<'_>>,
|
||||
@ -536,16 +532,16 @@ pub async fn change_password_route(
|
||||
|
||||
db.users.set_password(&sender_user, &body.new_password)?;
|
||||
|
||||
// TODO: Read logout_devices field when it's available and respect that, currently not supported in Ruma
|
||||
// See: https://github.com/ruma/ruma/issues/107
|
||||
// Logout all devices except the current one
|
||||
for id in db
|
||||
.users
|
||||
.all_device_ids(&sender_user)
|
||||
.filter_map(|id| id.ok())
|
||||
.filter(|id| id != sender_device)
|
||||
{
|
||||
db.users.remove_device(&sender_user, &id)?;
|
||||
if body.logout_devices {
|
||||
// Logout all devices except the current one
|
||||
for id in db
|
||||
.users
|
||||
.all_device_ids(&sender_user)
|
||||
.filter_map(|id| id.ok())
|
||||
.filter(|id| id != sender_device)
|
||||
{
|
||||
db.users.remove_device(&sender_user, &id)?;
|
||||
}
|
||||
}
|
||||
|
||||
db.flush().await?;
|
||||
@ -562,6 +558,7 @@ pub async fn change_password_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/account/whoami", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(body))]
|
||||
pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
Ok(whoami::Response {
|
||||
@ -582,6 +579,7 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/account/deactivate", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn deactivate_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<deactivate::Request<'_>>,
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice,
|
||||
@ -19,6 +20,7 @@ use rocket::{delete, get, put};
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/directory/room/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_alias_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_alias::Request<'_>>,
|
||||
@ -39,6 +41,7 @@ pub async fn create_alias_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/r0/directory/room/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_alias_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_alias::Request<'_>>,
|
||||
@ -54,6 +57,7 @@ pub async fn delete_alias_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/directory/room/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_alias_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_alias::Request<'_>>,
|
||||
@ -83,15 +87,23 @@ pub async fn get_alias_helper(
|
||||
Some(r) => room_id = Some(r),
|
||||
None => {
|
||||
for (_id, registration) in db.appservice.iter_all().filter_map(|r| r.ok()) {
|
||||
if db
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
&db.globals,
|
||||
registration,
|
||||
appservice::query::query_room_alias::v1::Request { room_alias },
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
let aliases = registration
|
||||
.get("namespaces")
|
||||
.and_then(|ns| ns.get("aliases"))
|
||||
.and_then(|users| users.get("regex"))
|
||||
.and_then(|regex| regex.as_str())
|
||||
.and_then(|regex| Regex::new(regex).ok());
|
||||
|
||||
if aliases.map_or(false, |aliases| aliases.is_match(room_alias.as_str()))
|
||||
&& db
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
&db.globals,
|
||||
registration,
|
||||
appservice::query::query_room_alias::v1::Request { room_alias },
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
room_id = Some(db.rooms.id_from_alias(&room_alias)?.ok_or_else(|| {
|
||||
Error::bad_config("Appservice lied to us. Room does not exist.")
|
||||
|
@ -17,6 +17,7 @@ use rocket::{delete, get, post, put};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/unstable/room_keys/version", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_backup::Request>,
|
||||
@ -35,6 +36,7 @@ pub async fn create_backup_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<update_backup::Request<'_>>,
|
||||
@ -52,6 +54,7 @@ pub async fn update_backup_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/unstable/room_keys/version", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_latest_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_latest_backup::Request>,
|
||||
@ -79,6 +82,7 @@ pub async fn get_latest_backup_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup::Request<'_>>,
|
||||
@ -105,6 +109,7 @@ pub async fn get_backup_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_backup::Request<'_>>,
|
||||
@ -123,6 +128,7 @@ pub async fn delete_backup_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/unstable/room_keys/keys", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<add_backup_keys::Request<'_>>,
|
||||
@ -156,6 +162,7 @@ pub async fn add_backup_keys_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<add_backup_key_sessions::Request<'_>>,
|
||||
@ -187,6 +194,7 @@ pub async fn add_backup_key_sessions_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn add_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<add_backup_key_session::Request<'_>>,
|
||||
@ -215,6 +223,7 @@ pub async fn add_backup_key_session_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/unstable/room_keys/keys", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup_keys::Request<'_>>,
|
||||
@ -230,6 +239,7 @@ pub async fn get_backup_keys_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup_key_sessions::Request<'_>>,
|
||||
@ -247,6 +257,7 @@ pub async fn get_backup_key_sessions_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup_key_session::Request<'_>>,
|
||||
@ -270,6 +281,7 @@ pub async fn get_backup_key_session_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/unstable/room_keys/keys", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_backup_keys::Request<'_>>,
|
||||
@ -292,6 +304,7 @@ pub async fn delete_backup_keys_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_key_sessions_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_backup_key_sessions::Request<'_>>,
|
||||
@ -314,6 +327,7 @@ pub async fn delete_backup_key_sessions_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_backup_key_session_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_backup_key_session::Request<'_>>,
|
||||
|
@ -1,5 +1,10 @@
|
||||
use crate::ConduitResult;
|
||||
use ruma::{api::client::r0::capabilities::get_capabilities, RoomVersionId};
|
||||
use ruma::{
|
||||
api::client::r0::capabilities::{
|
||||
get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability,
|
||||
},
|
||||
RoomVersionId,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@ -9,26 +14,17 @@ use rocket::get;
|
||||
///
|
||||
/// Get information on this server's supported feature set and other relevent capabilities.
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/capabilities"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn get_capabilities_route() -> ConduitResult<get_capabilities::Response> {
|
||||
let mut available = BTreeMap::new();
|
||||
available.insert(
|
||||
RoomVersionId::Version5,
|
||||
get_capabilities::RoomVersionStability::Stable,
|
||||
);
|
||||
available.insert(
|
||||
RoomVersionId::Version6,
|
||||
get_capabilities::RoomVersionStability::Stable,
|
||||
);
|
||||
available.insert(RoomVersionId::Version5, RoomVersionStability::Stable);
|
||||
available.insert(RoomVersionId::Version6, RoomVersionStability::Stable);
|
||||
|
||||
Ok(get_capabilities::Response {
|
||||
capabilities: get_capabilities::Capabilities {
|
||||
change_password: get_capabilities::ChangePasswordCapability::default(), // enabled by default
|
||||
room_versions: get_capabilities::RoomVersionsCapability {
|
||||
default: RoomVersionId::Version6,
|
||||
available,
|
||||
},
|
||||
custom_capabilities: BTreeMap::new(),
|
||||
},
|
||||
}
|
||||
.into())
|
||||
let mut capabilities = Capabilities::new();
|
||||
capabilities.room_versions = RoomVersionsCapability {
|
||||
default: RoomVersionId::Version6,
|
||||
available,
|
||||
};
|
||||
|
||||
Ok(get_capabilities::Response { capabilities }.into())
|
||||
}
|
||||
|
@ -16,13 +16,14 @@ use rocket::{get, put};
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_global_account_data_route(
|
||||
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 content = 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();
|
||||
@ -32,10 +33,7 @@ pub async fn set_global_account_data_route(
|
||||
sender_user,
|
||||
event_type.clone().into(),
|
||||
&BasicEvent {
|
||||
content: CustomEventContent {
|
||||
event_type,
|
||||
json: content,
|
||||
},
|
||||
content: CustomEventContent { event_type, data },
|
||||
},
|
||||
&db.globals,
|
||||
)?;
|
||||
@ -49,6 +47,7 @@ pub async fn set_global_account_data_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_global_account_data_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_global_account_data::Request<'_>>,
|
||||
|
@ -10,6 +10,7 @@ use rocket::get;
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/context/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_context_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_context::Request<'_>>,
|
||||
|
@ -16,6 +16,7 @@ use rocket::{delete, get, post, put};
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/devices", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_devices_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_devices::Request>,
|
||||
@ -35,6 +36,7 @@ pub async fn get_devices_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/devices/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_device_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_device::Request<'_>>,
|
||||
@ -53,6 +55,7 @@ pub async fn get_device_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/devices/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_device_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<update_device::Request<'_>>,
|
||||
@ -78,6 +81,7 @@ pub async fn update_device_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/r0/devices/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_device_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_device::Request<'_>>,
|
||||
@ -126,6 +130,7 @@ pub async fn delete_device_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/delete_devices", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_devices_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_devices::Request<'_>>,
|
||||
|
@ -21,7 +21,7 @@ use ruma::{
|
||||
EventType,
|
||||
},
|
||||
serde::Raw,
|
||||
ServerName,
|
||||
ServerName, UInt,
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
@ -31,6 +31,7 @@ use rocket::{get, post, put};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/publicRooms", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_public_rooms_filtered_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_public_rooms_filtered::Request<'_>>,
|
||||
@ -50,6 +51,7 @@ pub async fn get_public_rooms_filtered_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/publicRooms", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_public_rooms_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_public_rooms::Request<'_>>,
|
||||
@ -78,6 +80,7 @@ pub async fn get_public_rooms_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_room_visibility_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_room_visibility::Request<'_>>,
|
||||
@ -107,6 +110,7 @@ pub async fn set_room_visibility_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_room_visibility_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_room_visibility::Request<'_>>,
|
||||
@ -124,7 +128,7 @@ pub async fn get_room_visibility_route(
|
||||
pub async fn get_public_rooms_filtered_helper(
|
||||
db: &Database,
|
||||
server: Option<&ServerName>,
|
||||
limit: Option<ruma::UInt>,
|
||||
limit: Option<UInt>,
|
||||
since: Option<&str>,
|
||||
filter: &IncomingFilter,
|
||||
_network: &IncomingRoomNetwork,
|
||||
|
@ -5,6 +5,7 @@ use ruma::api::client::r0::filter::{self, create_filter, get_filter};
|
||||
use rocket::{get, post};
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/filter/<_>"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn get_filter_route() -> ConduitResult<get_filter::Response> {
|
||||
// TODO
|
||||
Ok(get_filter::Response::new(filter::IncomingFilterDefinition {
|
||||
@ -18,6 +19,7 @@ pub async fn get_filter_route() -> ConduitResult<get_filter::Response> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/user/<_>/filter"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn create_filter_route() -> ConduitResult<create_filter::Response> {
|
||||
// TODO
|
||||
Ok(create_filter::Response::new(utils::random_string(10)).into())
|
||||
|
@ -22,6 +22,7 @@ use rocket::{get, post};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/keys/upload", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upload_keys::Request>,
|
||||
@ -70,6 +71,7 @@ pub async fn upload_keys_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/keys/query", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_keys::Request<'_>>,
|
||||
@ -150,6 +152,7 @@ pub async fn get_keys_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/keys/claim", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn claim_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<claim_keys::Request>,
|
||||
@ -183,6 +186,7 @@ pub async fn claim_keys_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/unstable/keys/device_signing/upload", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_signing_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upload_signing_keys::Request<'_>>,
|
||||
@ -240,6 +244,7 @@ pub async fn upload_signing_keys_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/unstable/keys/signatures/upload", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upload_signatures_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upload_signatures::Request>,
|
||||
@ -300,6 +305,7 @@ pub async fn upload_signatures_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/keys/changes", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_key_changes_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_key_changes::Request<'_>>,
|
||||
|
@ -12,6 +12,7 @@ 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<'_, Database>,
|
||||
) -> ConduitResult<get_media_config::Response> {
|
||||
@ -25,6 +26,7 @@ pub async fn get_media_config_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/media/r0/upload", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_content_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_content::Request<'_>>,
|
||||
@ -54,6 +56,7 @@ pub async fn create_content_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/media/r0/download/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_content_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_content::Request<'_>>,
|
||||
@ -103,6 +106,7 @@ pub async fn get_content_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/media/r0/thumbnail/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_content_thumbnail_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_content_thumbnail::Request<'_>>,
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
pdu::{PduBuilder, PduEvent},
|
||||
utils, ConduitResult, Database, Error, Result, Ruma,
|
||||
};
|
||||
use log::warn;
|
||||
use log::{info, warn};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
@ -21,11 +21,9 @@ use ruma::{
|
||||
serde::{to_canonical_value, CanonicalJsonObject, Raw},
|
||||
EventId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use state_res::Event;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{BTreeMap, HashMap},
|
||||
convert::TryFrom,
|
||||
iter,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@ -36,6 +34,7 @@ use rocket::{get, post};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/join", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn join_room_by_id_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<join_room_by_id::Request<'_>>,
|
||||
@ -54,6 +53,7 @@ pub async fn join_room_by_id_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/join/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn join_room_by_id_or_alias_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<join_room_by_id_or_alias::Request<'_>>,
|
||||
@ -88,6 +88,7 @@ pub async fn join_room_by_id_or_alias_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/leave", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn leave_room_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<leave_room::Request<'_>>,
|
||||
@ -136,6 +137,7 @@ pub async fn leave_room_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/invite", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn invite_user_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<invite_user::Request<'_>>,
|
||||
@ -175,6 +177,7 @@ pub async fn invite_user_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/kick", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn kick_user_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<kick_user::Request<'_>>,
|
||||
@ -224,6 +227,7 @@ pub async fn kick_user_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/ban", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn ban_user_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<ban_user::Request<'_>>,
|
||||
@ -280,6 +284,7 @@ pub async fn ban_user_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/unban", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn unban_user_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<unban_user::Request<'_>>,
|
||||
@ -328,6 +333,7 @@ pub async fn unban_user_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/forget", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn forget_room_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<forget_room::Request<'_>>,
|
||||
@ -345,6 +351,7 @@ pub async fn forget_room_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/joined_rooms", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn joined_rooms_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<joined_rooms::Request>,
|
||||
@ -365,6 +372,7 @@ pub async fn joined_rooms_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/members", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_member_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_member_events::Request<'_>>,
|
||||
@ -394,6 +402,7 @@ pub async fn get_member_events_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/joined_members", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn joined_members_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<joined_members::Request<'_>>,
|
||||
@ -428,6 +437,7 @@ pub async fn joined_members_route(
|
||||
Ok(joined_members::Response { joined }.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(db))]
|
||||
async fn join_room_by_id_helper(
|
||||
db: &Database,
|
||||
sender_user: Option<&UserId>,
|
||||
@ -555,23 +565,22 @@ async fn join_room_by_id_helper(
|
||||
Ok((event_id, value))
|
||||
};
|
||||
|
||||
let room_state = send_join_response.room_state.state.iter().map(add_event_id);
|
||||
let count = db.globals.next_count()?;
|
||||
|
||||
let state_events = room_state
|
||||
.clone()
|
||||
.map(|pdu: Result<(EventId, CanonicalJsonObject)>| Ok(pdu?.0))
|
||||
.chain(iter::once(Ok(event_id.clone()))) // Add join event we just created
|
||||
.collect::<Result<HashSet<EventId>>>()?;
|
||||
let mut pdu_id = room_id.as_bytes().to_vec();
|
||||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&count.to_be_bytes());
|
||||
|
||||
let auth_chain = send_join_response
|
||||
let pdu = PduEvent::from_id_val(&event_id, join_event.clone())
|
||||
.map_err(|_| Error::BadServerResponse("Invalid PDU in send_join response."))?;
|
||||
|
||||
let mut state = HashMap::new();
|
||||
|
||||
for pdu in send_join_response
|
||||
.room_state
|
||||
.auth_chain
|
||||
.state
|
||||
.iter()
|
||||
.map(add_event_id);
|
||||
|
||||
let mut event_map = room_state
|
||||
.chain(auth_chain)
|
||||
.chain(iter::once(Ok((event_id, join_event)))) // Add join event we just created
|
||||
.map(add_event_id)
|
||||
.map(|r| {
|
||||
let (event_id, value) = r?;
|
||||
PduEvent::from_id_val(&event_id, value.clone())
|
||||
@ -581,103 +590,78 @@ async fn join_room_by_id_helper(
|
||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
||||
})
|
||||
})
|
||||
.collect::<Result<BTreeMap<EventId, Arc<PduEvent>>>>()?;
|
||||
|
||||
let control_events = event_map
|
||||
.values()
|
||||
.filter(|pdu| state_res::is_power_event(pdu))
|
||||
.map(|pdu| pdu.event_id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// These events are not guaranteed to be sorted but they are resolved according to spec
|
||||
// we auth them anyways to weed out faulty/malicious server. The following is basically the
|
||||
// full state resolution algorithm.
|
||||
let event_ids = event_map.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
let sorted_control_events = state_res::StateResolution::reverse_topological_power_sort(
|
||||
&room_id,
|
||||
&control_events,
|
||||
&mut event_map,
|
||||
&event_ids,
|
||||
);
|
||||
|
||||
// Auth check each event against the "partial" state created by the preceding events
|
||||
let resolved_control_events = state_res::StateResolution::iterative_auth_check(
|
||||
room_id,
|
||||
&RoomVersionId::Version6,
|
||||
&sorted_control_events,
|
||||
&BTreeMap::new(), // We have no "clean/resolved" events to add (these extend the `resolved_control_events`)
|
||||
&mut event_map,
|
||||
)
|
||||
.expect("iterative auth check failed on resolved events");
|
||||
|
||||
// This removes the control events that failed auth, leaving the resolved
|
||||
// to be mainline sorted. In the actual `state_res::StateResolution::resolve`
|
||||
// function both are removed since these are all events we don't know of
|
||||
// we must keep track of everything to add to our DB.
|
||||
let events_to_sort = event_map
|
||||
.keys()
|
||||
.filter(|id| {
|
||||
!sorted_control_events.contains(id)
|
||||
|| resolved_control_events.values().any(|rid| *id == rid)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let power_level =
|
||||
resolved_control_events.get(&(EventType::RoomPowerLevels, Some("".to_string())));
|
||||
// Sort the remaining non control events
|
||||
let sorted_event_ids = state_res::StateResolution::mainline_sort(
|
||||
room_id,
|
||||
&events_to_sort,
|
||||
power_level,
|
||||
&mut event_map,
|
||||
);
|
||||
|
||||
let resolved_events = state_res::StateResolution::iterative_auth_check(
|
||||
room_id,
|
||||
&RoomVersionId::Version6,
|
||||
&sorted_event_ids,
|
||||
&resolved_control_events,
|
||||
&mut event_map,
|
||||
)
|
||||
.expect("iterative auth check failed on resolved events");
|
||||
|
||||
let mut state = HashMap::new();
|
||||
|
||||
// filter the events that failed the auth check keeping the remaining events
|
||||
// sorted correctly
|
||||
for ev_id in sorted_event_ids
|
||||
.iter()
|
||||
.filter(|id| resolved_events.values().any(|rid| rid == *id))
|
||||
{
|
||||
let pdu = event_map
|
||||
.get(ev_id)
|
||||
.expect("Found event_id in sorted events that is not in resolved state");
|
||||
let (id, pdu) = pdu?;
|
||||
info!("adding {} to outliers: {:#?}", id, pdu);
|
||||
db.rooms.add_pdu_outlier(&pdu)?;
|
||||
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(|_| {
|
||||
Error::BadServerResponse("Invalid user id in send_join response.")
|
||||
})?;
|
||||
|
||||
// We do not rebuild the PDU in this case only insert to DB
|
||||
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.clone().into(),
|
||||
// TODO: can we simplify the DAG or should we copy it exactly??
|
||||
&pdu.prev_events,
|
||||
&db,
|
||||
)?;
|
||||
|
||||
if state_events.contains(ev_id) {
|
||||
if let Some(key) = &pdu.state_key {
|
||||
state.insert((pdu.kind(), key.to_string()), pdu_id);
|
||||
// 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::MemberEventContent>(pdu.content.clone())
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid member event content.",
|
||||
)
|
||||
})?,
|
||||
&pdu.sender,
|
||||
&db.account_data,
|
||||
&db.globals,
|
||||
)?;
|
||||
}
|
||||
let mut long_id = room_id.as_bytes().to_vec();
|
||||
long_id.push(0xff);
|
||||
long_id.extend_from_slice(id.as_bytes());
|
||||
state.insert((pdu.kind.clone(), state_key.clone()), long_id);
|
||||
}
|
||||
}
|
||||
|
||||
state.insert(
|
||||
(
|
||||
pdu.kind.clone(),
|
||||
pdu.state_key.clone().expect("join event has state key"),
|
||||
),
|
||||
pdu_id.clone(),
|
||||
);
|
||||
|
||||
db.rooms.force_state(room_id, state, &db.globals)?;
|
||||
|
||||
for pdu in send_join_response
|
||||
.room_state
|
||||
.auth_chain
|
||||
.iter()
|
||||
.map(add_event_id)
|
||||
.map(|r| {
|
||||
let (event_id, value) = r?;
|
||||
PduEvent::from_id_val(&event_id, value.clone())
|
||||
.map(|ev| (event_id, Arc::new(ev)))
|
||||
.map_err(|e| {
|
||||
warn!("{:?}: {}", value, e);
|
||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
||||
})
|
||||
})
|
||||
{
|
||||
let (id, pdu) = pdu?;
|
||||
info!("adding {} to outliers: {:#?}", id, pdu);
|
||||
db.rooms.add_pdu_outlier(&pdu)?;
|
||||
}
|
||||
|
||||
db.rooms.append_pdu(
|
||||
&pdu,
|
||||
utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"),
|
||||
db.globals.next_count()?,
|
||||
pdu_id.into(),
|
||||
&[pdu.event_id.clone()],
|
||||
db,
|
||||
)?;
|
||||
} else {
|
||||
let event = member::MemberEventContent {
|
||||
membership: member::MembershipState::Join,
|
||||
|
@ -20,6 +20,7 @@ use rocket::{get, put};
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_message_event_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<send_message_event::Request<'_>>,
|
||||
@ -87,6 +88,7 @@ pub async fn send_message_event_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/messages", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_message_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_message_events::Request<'_>>,
|
||||
|
@ -75,6 +75,7 @@ const SESSION_ID_LENGTH: usize = 256;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
#[options("/<_..>")]
|
||||
#[tracing::instrument]
|
||||
pub async fn options_route() -> ConduitResult<send_event_to_device::Response> {
|
||||
Ok(send_event_to_device::Response.into())
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use rocket::put;
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/presence/<_>/status", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_presence_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_presence::Request<'_>>,
|
||||
|
@ -19,6 +19,7 @@ use std::convert::TryInto;
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_displayname_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_display_name::Request<'_>>,
|
||||
@ -98,6 +99,7 @@ pub async fn set_displayname_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_displayname_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_display_name::Request<'_>>,
|
||||
@ -112,6 +114,7 @@ pub async fn get_displayname_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_avatar_url_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_avatar_url::Request<'_>>,
|
||||
@ -191,6 +194,7 @@ pub async fn set_avatar_url_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_avatar_url_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_avatar_url::Request<'_>>,
|
||||
@ -205,6 +209,7 @@ pub async fn get_avatar_url_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/profile/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_profile_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_profile::Request<'_>>,
|
||||
|
@ -23,6 +23,7 @@ use rocket::{delete, get, post, put};
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/pushrules", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrules_all_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_pushrules_all::Request>,
|
||||
@ -47,6 +48,7 @@ pub async fn get_pushrules_all_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_pushrule::Request<'_>>,
|
||||
@ -105,6 +107,7 @@ pub async fn get_pushrule_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_pushrule::Request<'_>>,
|
||||
@ -251,6 +254,7 @@ pub async fn set_pushrule_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_actions_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_pushrule_actions::Request<'_>>,
|
||||
@ -314,6 +318,7 @@ pub async fn get_pushrule_actions_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushrule_actions_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_pushrule_actions::Request<'_>>,
|
||||
@ -417,6 +422,7 @@ pub async fn set_pushrule_actions_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushrule_enabled_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_pushrule_enabled::Request<'_>>,
|
||||
@ -477,6 +483,7 @@ pub async fn get_pushrule_enabled_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushrule_enabled_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_pushrule_enabled::Request<'_>>,
|
||||
@ -492,7 +499,7 @@ pub async fn set_pushrule_enabled_route(
|
||||
|
||||
let mut event = db
|
||||
.account_data
|
||||
.get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
|
||||
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"PushRules event not found.",
|
||||
@ -580,6 +587,7 @@ pub async fn set_pushrule_enabled_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_pushrule::Request<'_>>,
|
||||
@ -673,6 +681,7 @@ pub async fn delete_pushrule_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/pushers", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_pushers_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_pushers::Request>,
|
||||
@ -689,6 +698,7 @@ pub async fn get_pushers_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/pushers/set", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_pushers_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_pusher::Request>,
|
||||
|
@ -3,7 +3,9 @@ use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{capabilities::get_capabilities, read_marker::set_read_marker},
|
||||
r0::{
|
||||
capabilities::get_capabilities, read_marker::set_read_marker, receipt::create_receipt,
|
||||
},
|
||||
},
|
||||
events::{AnyEphemeralRoomEvent, AnyEvent, EventType},
|
||||
};
|
||||
@ -16,6 +18,7 @@ use std::{collections::BTreeMap, time::SystemTime};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/read_markers", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn set_read_marker_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_read_marker::Request<'_>>,
|
||||
@ -84,13 +87,53 @@ pub async fn set_read_marker_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/receipt/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
pub async fn set_receipt_route(
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_receipt_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_capabilities::Request>,
|
||||
) -> ConduitResult<set_read_marker::Response> {
|
||||
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
body: Ruma<create_receipt::Request<'_>>,
|
||||
) -> ConduitResult<create_receipt::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
db.rooms.edus.private_read_set(
|
||||
&body.room_id,
|
||||
&sender_user,
|
||||
db.rooms
|
||||
.get_pdu_count(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Event does not exist.",
|
||||
))?,
|
||||
&db.globals,
|
||||
)?;
|
||||
|
||||
let mut user_receipts = BTreeMap::new();
|
||||
user_receipts.insert(
|
||||
sender_user.clone(),
|
||||
ruma::events::receipt::Receipt {
|
||||
ts: Some(SystemTime::now()),
|
||||
},
|
||||
);
|
||||
let mut receipt_content = BTreeMap::new();
|
||||
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,
|
||||
AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
|
||||
ruma::events::receipt::ReceiptEvent {
|
||||
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
|
||||
room_id: body.room_id.clone(),
|
||||
},
|
||||
)),
|
||||
&db.globals,
|
||||
)?;
|
||||
|
||||
db.flush().await?;
|
||||
|
||||
Ok(set_read_marker::Response.into())
|
||||
Ok(create_receipt::Response.into())
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use rocket::put;
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn redact_event_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<redact_event::Request<'_>>,
|
||||
|
@ -22,6 +22,7 @@ use rocket::{get, post};
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/createRoom", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn create_room_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_room::Request<'_>>,
|
||||
@ -306,6 +307,7 @@ pub async fn create_room_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/event/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_room_event_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_room_event::Request<'_>>,
|
||||
@ -333,6 +335,7 @@ pub async fn get_room_event_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_room_id>/upgrade", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn upgrade_room_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upgrade_room::Request<'_>>,
|
||||
|
@ -11,6 +11,7 @@ use std::collections::BTreeMap;
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/search", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn search_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<search_events::Request<'_>>,
|
||||
|
@ -8,6 +8,13 @@ use ruma::{
|
||||
},
|
||||
UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
exp: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post};
|
||||
@ -17,6 +24,7 @@ use rocket::{get, post};
|
||||
/// Get the homeserver's supported login types. One of these should be used as the `type` field
|
||||
/// when logging in.
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
|
||||
Ok(get_login_types::Response::new(vec![get_login_types::LoginType::Password]).into())
|
||||
}
|
||||
@ -35,49 +43,71 @@ pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response>
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/login", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn login_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<login::Request<'_>>,
|
||||
) -> ConduitResult<login::Response> {
|
||||
// Validate login method
|
||||
let user_id =
|
||||
// TODO: Other login methods
|
||||
if let (login::IncomingUserInfo::MatrixId(username), login::IncomingLoginInfo::Password { password }) =
|
||||
(&body.user, &body.login_info)
|
||||
{
|
||||
let user_id = UserId::parse_with_server_name(username.to_string(), db.globals.server_name())
|
||||
.map_err(|_| Error::BadRequest(
|
||||
ErrorKind::InvalidUsername,
|
||||
"Username is invalid."
|
||||
))?;
|
||||
let hash = db.users.password_hash(&user_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Wrong username or password."
|
||||
))?;
|
||||
// TODO: Other login methods
|
||||
let user_id = match &body.login_info {
|
||||
login::IncomingLoginInfo::Password { password } => {
|
||||
let username = if let login::IncomingUserInfo::MatrixId(matrix_id) = &body.user {
|
||||
matrix_id
|
||||
} else {
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||
};
|
||||
let user_id =
|
||||
UserId::parse_with_server_name(username.to_owned(), db.globals.server_name())
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
|
||||
})?;
|
||||
let hash = db.users.password_hash(&user_id)?.ok_or(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Wrong username or password.",
|
||||
))?;
|
||||
|
||||
if hash.is_empty() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UserDeactivated,
|
||||
"The user has been deactivated"
|
||||
"The user has been deactivated",
|
||||
));
|
||||
}
|
||||
|
||||
let hash_matches =
|
||||
argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false);
|
||||
let hash_matches = argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false);
|
||||
|
||||
if !hash_matches {
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Wrong username or password."));
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Wrong username or password.",
|
||||
));
|
||||
}
|
||||
|
||||
user_id
|
||||
} else {
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||
};
|
||||
}
|
||||
login::IncomingLoginInfo::Token { token } => {
|
||||
if let Some(jwt_decoding_key) = db.globals.jwt_decoding_key() {
|
||||
let token = jsonwebtoken::decode::<Claims>(
|
||||
&token,
|
||||
&jwt_decoding_key,
|
||||
&jsonwebtoken::Validation::default(),
|
||||
)
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Token is invalid."))?;
|
||||
let username = token.claims.sub;
|
||||
UserId::parse_with_server_name(username, db.globals.server_name()).map_err(
|
||||
|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."),
|
||||
)?
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Token login is not supported (server has no jwt decoding key).",
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
@ -85,14 +115,23 @@ pub async fn login_route(
|
||||
// Generate a new token for the device
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// TODO: Don't always create a new device
|
||||
// Add device
|
||||
db.users.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&token,
|
||||
body.initial_device_display_name.clone(),
|
||||
)?;
|
||||
// Determine if device_id was provided and exists in the db for this user
|
||||
let device_exists = body.device_id.as_ref().map_or(false, |device_id| {
|
||||
db.users
|
||||
.all_device_ids(&user_id)
|
||||
.any(|x| x.as_ref().map_or(false, |v| v == device_id))
|
||||
});
|
||||
|
||||
if device_exists {
|
||||
db.users.set_token(&user_id, &device_id, &token)?;
|
||||
} else {
|
||||
db.users.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&token,
|
||||
body.initial_device_display_name.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
info!("{} logged in", user_id);
|
||||
|
||||
@ -118,6 +157,7 @@ pub async fn login_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/logout", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn logout_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<logout::Request>,
|
||||
@ -145,6 +185,7 @@ pub async fn logout_route(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/logout/all", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn logout_all_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<logout_all::Request>,
|
||||
|
@ -22,6 +22,7 @@ use rocket::{get, put};
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_state_event_for_key_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<send_state_event_for_key::Request<'_>>,
|
||||
@ -55,6 +56,7 @@ pub async fn send_state_event_for_key_route(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_state_event_for_empty_key_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<send_state_event_for_empty_key::Request<'_>>,
|
||||
@ -96,6 +98,7 @@ pub async fn send_state_event_for_empty_key_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/state", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_state_events::Request<'_>>,
|
||||
@ -142,6 +145,7 @@ pub async fn get_state_events_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_for_key_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_state_events_for_key::Request<'_>>,
|
||||
@ -193,6 +197,7 @@ pub async fn get_state_events_for_key_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_state_events_for_empty_key_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_state_events_for_empty_key::Request<'_>>,
|
||||
@ -234,7 +239,7 @@ pub async fn get_state_events_for_empty_key_route(
|
||||
.1;
|
||||
|
||||
Ok(get_state_events_for_empty_key::Response {
|
||||
content: serde_json::value::to_raw_value(&event)
|
||||
content: serde_json::value::to_raw_value(&event.content)
|
||||
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
|
||||
}
|
||||
.into())
|
||||
|
@ -30,6 +30,7 @@ use std::{
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/sync", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn sync_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<sync_events::Request<'_>>,
|
||||
@ -95,89 +96,102 @@ pub async fn sync_events_route(
|
||||
|
||||
// Database queries:
|
||||
|
||||
let current_state = db.rooms.room_state_full(&room_id)?;
|
||||
let current_members = current_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.map(|(key, value)| (&key.1, value)) // Only keep state key
|
||||
.collect::<Vec<_>>();
|
||||
let encrypted_room = current_state
|
||||
.get(&(EventType::RoomEncryption, "".to_owned()))
|
||||
.is_some();
|
||||
let current_state_hash = db.rooms.current_state_hash(&room_id)?;
|
||||
|
||||
// These type is Option<Option<_>>. The outer Option is None when there is no event between
|
||||
// since and the current room state, meaning there should be no updates.
|
||||
// The inner Option is None when there is an event, but there is no state hash associated
|
||||
// with it. This can happen for the RoomCreate event, so all updates should arrive.
|
||||
let first_pdu_after_since = db.rooms.pdus_after(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)
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
let since_state_hash = first_pdu_after_since
|
||||
let since_state_hash = first_pdu_before_since
|
||||
.as_ref()
|
||||
.map(|pdu| db.rooms.pdu_state_hash(&pdu.as_ref().ok()?.0).ok()?);
|
||||
|
||||
let since_state = since_state_hash.as_ref().map(|state_hash| {
|
||||
state_hash
|
||||
.as_ref()
|
||||
.and_then(|state_hash| db.rooms.state_full(&room_id, &state_hash).ok())
|
||||
});
|
||||
let (
|
||||
heroes,
|
||||
joined_member_count,
|
||||
invited_member_count,
|
||||
joined_since_last_sync,
|
||||
state_events,
|
||||
) = if pdus_after_since && Some(¤t_state_hash) != since_state_hash.as_ref() {
|
||||
let current_state = db.rooms.room_state_full(&room_id)?;
|
||||
let current_members = current_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.map(|(key, value)| (&key.1, value)) // Only keep state key
|
||||
.collect::<Vec<_>>();
|
||||
let encrypted_room = current_state
|
||||
.get(&(EventType::RoomEncryption, "".to_owned()))
|
||||
.is_some();
|
||||
let since_state = since_state_hash.as_ref().map(|state_hash| {
|
||||
state_hash
|
||||
.as_ref()
|
||||
.and_then(|state_hash| db.rooms.state_full(&room_id, &state_hash).ok())
|
||||
});
|
||||
|
||||
let since_encryption = since_state.as_ref().map(|state| {
|
||||
state
|
||||
.as_ref()
|
||||
.map(|state| state.get(&(EventType::RoomEncryption, "".to_owned())))
|
||||
});
|
||||
|
||||
// Calculations:
|
||||
let new_encrypted_room =
|
||||
encrypted_room && since_encryption.map_or(false, |encryption| encryption.is_none());
|
||||
|
||||
let send_member_count = since_state.as_ref().map_or(false, |since_state| {
|
||||
since_state.as_ref().map_or(true, |since_state| {
|
||||
current_members.len()
|
||||
!= since_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.count()
|
||||
})
|
||||
});
|
||||
|
||||
let since_sender_member = since_state.as_ref().map(|since_state| {
|
||||
since_state.as_ref().and_then(|state| {
|
||||
let since_encryption = since_state.as_ref().map(|state| {
|
||||
state
|
||||
.get(&(EventType::RoomMember, sender_user.as_str().to_owned()))
|
||||
.and_then(|pdu| {
|
||||
serde_json::from_value::<
|
||||
.as_ref()
|
||||
.map(|state| state.get(&(EventType::RoomEncryption, "".to_owned())))
|
||||
});
|
||||
|
||||
// Calculations:
|
||||
let new_encrypted_room =
|
||||
encrypted_room && since_encryption.map_or(true, |encryption| encryption.is_none());
|
||||
|
||||
let send_member_count = since_state.as_ref().map_or(true, |since_state| {
|
||||
since_state.as_ref().map_or(true, |since_state| {
|
||||
current_members.len()
|
||||
!= since_state
|
||||
.iter()
|
||||
.filter(|(key, _)| key.0 == EventType::RoomMember)
|
||||
.count()
|
||||
})
|
||||
});
|
||||
|
||||
let since_sender_member = since_state.as_ref().map(|since_state| {
|
||||
since_state.as_ref().and_then(|state| {
|
||||
state
|
||||
.get(&(EventType::RoomMember, sender_user.as_str().to_owned()))
|
||||
.and_then(|pdu| {
|
||||
serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if encrypted_room {
|
||||
for (user_id, current_member) in current_members {
|
||||
let current_membership = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(current_member.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?
|
||||
.membership;
|
||||
if encrypted_room {
|
||||
for (user_id, current_member) in current_members {
|
||||
let current_membership = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(current_member.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?
|
||||
.membership;
|
||||
|
||||
let since_membership =
|
||||
since_state
|
||||
.as_ref()
|
||||
.map_or(MembershipState::Join, |since_state| {
|
||||
since_state
|
||||
.as_ref()
|
||||
.and_then(|since_state| {
|
||||
since_state
|
||||
.get(&(EventType::RoomMember, user_id.clone()))
|
||||
.and_then(|since_member| {
|
||||
serde_json::from_value::<
|
||||
let since_membership =
|
||||
since_state
|
||||
.as_ref()
|
||||
.map_or(MembershipState::Leave, |since_state| {
|
||||
since_state
|
||||
.as_ref()
|
||||
.and_then(|since_state| {
|
||||
since_state
|
||||
.get(&(EventType::RoomMember, user_id.clone()))
|
||||
.and_then(|since_member| {
|
||||
serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(
|
||||
since_member.content.clone()
|
||||
@ -188,50 +202,157 @@ pub async fn sync_events_route(
|
||||
Error::bad_database("Invalid PDU in database.")
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
.map_or(MembershipState::Leave, |member| member.membership)
|
||||
});
|
||||
})
|
||||
})
|
||||
.map_or(MembershipState::Leave, |member| member.membership)
|
||||
});
|
||||
|
||||
let user_id = UserId::try_from(user_id.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
|
||||
let user_id = UserId::try_from(user_id.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
|
||||
|
||||
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) {
|
||||
device_list_updates.insert(user_id);
|
||||
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) {
|
||||
device_list_updates.insert(user_id);
|
||||
}
|
||||
}
|
||||
(MembershipState::Join, MembershipState::Leave) => {
|
||||
// Write down users that have left encrypted rooms we are in
|
||||
left_encrypted_users.insert(user_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(MembershipState::Join, MembershipState::Leave) => {
|
||||
// Write down users that have left encrypted rooms we are in
|
||||
left_encrypted_users.insert(user_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let joined_since_last_sync = since_sender_member.map_or(false, |member| {
|
||||
member.map_or(true, |member| member.membership != MembershipState::Join)
|
||||
});
|
||||
let joined_since_last_sync = since_sender_member.map_or(true, |member| {
|
||||
member.map_or(true, |member| member.membership != MembershipState::Join)
|
||||
});
|
||||
|
||||
if joined_since_last_sync && encrypted_room || new_encrypted_room {
|
||||
// If the user is in a new encrypted room, give them all joined users
|
||||
device_list_updates.extend(
|
||||
db.rooms
|
||||
.room_members(&room_id)
|
||||
.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
|
||||
})
|
||||
.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)
|
||||
}),
|
||||
);
|
||||
}
|
||||
if joined_since_last_sync && encrypted_room || new_encrypted_room {
|
||||
// If the user is in a new encrypted room, give them all joined users
|
||||
device_list_updates.extend(
|
||||
db.rooms
|
||||
.room_members(&room_id)
|
||||
.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
|
||||
})
|
||||
.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)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
|
||||
|
||||
// Recalculate heroes (first 5 members)
|
||||
let mut heroes = Vec::new();
|
||||
|
||||
if joined_member_count + invited_member_count <= 5 {
|
||||
// Go through all PDUs and for each member event, check if the user is still joined or
|
||||
// invited until we have 5 or we reach the end
|
||||
|
||||
for hero in db
|
||||
.rooms
|
||||
.all_pdus(&sender_user, &room_id)?
|
||||
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
|
||||
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
|
||||
.map(|(_, pdu)| {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid member event in database.")
|
||||
})?;
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let user_id =
|
||||
UserId::try_from(state_key.clone()).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId in member PDU.")
|
||||
})?;
|
||||
|
||||
// The membership was and still is invite or join
|
||||
if matches!(
|
||||
content.membership,
|
||||
MembershipState::Join | MembershipState::Invite
|
||||
) && (db.rooms.is_joined(&user_id, &room_id)?
|
||||
|| db.rooms.is_invited(&user_id, &room_id)?)
|
||||
{
|
||||
Ok::<_, Error>(Some(state_key.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.filter_map(|u| u.ok()) // Filter out buggy users
|
||||
// Filter for possible heroes
|
||||
.filter_map(|u| u)
|
||||
{
|
||||
if heroes.contains(&hero) || hero == sender_user.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
heroes.push(hero);
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Some(joined_member_count),
|
||||
Some(invited_member_count),
|
||||
heroes,
|
||||
)
|
||||
} else {
|
||||
(None, None, Vec::new())
|
||||
};
|
||||
|
||||
let state_events = if dbg!(joined_since_last_sync) {
|
||||
current_state
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect()
|
||||
} else {
|
||||
match since_state {
|
||||
None => Vec::new(),
|
||||
Some(Some(since_state)) => current_state
|
||||
.iter()
|
||||
.filter(|(key, value)| {
|
||||
since_state.get(key).map(|e| &e.event_id) != Some(&value.event_id)
|
||||
})
|
||||
.filter(|(_, value)| {
|
||||
!timeline_pdus.iter().any(|(_, timeline_pdu)| {
|
||||
timeline_pdu.kind == value.kind
|
||||
&& timeline_pdu.state_key == value.state_key
|
||||
})
|
||||
})
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
Some(None) => current_state
|
||||
.iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
heroes,
|
||||
joined_member_count,
|
||||
invited_member_count,
|
||||
joined_since_last_sync,
|
||||
state_events,
|
||||
)
|
||||
} else {
|
||||
(Vec::new(), None, None, false, Vec::new())
|
||||
};
|
||||
|
||||
// Look for device list updates in this room
|
||||
device_list_updates.extend(
|
||||
@ -240,71 +361,6 @@ pub async fn sync_events_route(
|
||||
.filter_map(|r| r.ok()),
|
||||
);
|
||||
|
||||
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
let invited_member_count = db.rooms.room_members_invited(&room_id).count();
|
||||
|
||||
// Recalculate heroes (first 5 members)
|
||||
let mut heroes = Vec::new();
|
||||
|
||||
if joined_member_count + invited_member_count <= 5 {
|
||||
// Go through all PDUs and for each member event, check if the user is still joined or
|
||||
// invited until we have 5 or we reach the end
|
||||
|
||||
for hero in db
|
||||
.rooms
|
||||
.all_pdus(&sender_user, &room_id)?
|
||||
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
|
||||
.filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
|
||||
.map(|(_, pdu)| {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let user_id = UserId::try_from(state_key.clone()).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId in member PDU.")
|
||||
})?;
|
||||
|
||||
// The membership was and still is invite or join
|
||||
if matches!(
|
||||
content.membership,
|
||||
MembershipState::Join | MembershipState::Invite
|
||||
) && (db.rooms.is_joined(&user_id, &room_id)?
|
||||
|| db.rooms.is_invited(&user_id, &room_id)?)
|
||||
{
|
||||
Ok::<_, Error>(Some(state_key.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.filter_map(|u| u.ok()) // Filter out buggy users
|
||||
// Filter for possible heroes
|
||||
.filter_map(|u| u)
|
||||
{
|
||||
if heroes.contains(&hero) || hero == sender_user.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
heroes.push(hero);
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Some(joined_member_count),
|
||||
Some(invited_member_count),
|
||||
heroes,
|
||||
)
|
||||
} else {
|
||||
(None, None, Vec::new())
|
||||
};
|
||||
|
||||
let notification_count = if send_notification_counts {
|
||||
if let Some(last_read) = db.rooms.edus.private_read_get(&room_id, &sender_user)? {
|
||||
Some(
|
||||
@ -385,34 +441,7 @@ pub async fn sync_events_route(
|
||||
events: room_events,
|
||||
},
|
||||
state: sync_events::State {
|
||||
events: if joined_since_last_sync {
|
||||
db.rooms
|
||||
.room_state_full(&room_id)?
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect()
|
||||
} else {
|
||||
match since_state {
|
||||
None => Vec::new(),
|
||||
Some(Some(since_state)) => current_state
|
||||
.iter()
|
||||
.filter(|(key, value)| {
|
||||
since_state.get(key).map(|e| &e.event_id) != Some(&value.event_id)
|
||||
})
|
||||
.filter(|(_, value)| {
|
||||
!timeline_pdus.iter().any(|(_, timeline_pdu)| {
|
||||
timeline_pdu.kind == value.kind
|
||||
&& timeline_pdu.state_key == value.state_key
|
||||
})
|
||||
})
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
Some(None) => current_state
|
||||
.iter()
|
||||
.map(|(_, pdu)| pdu.to_sync_state_event())
|
||||
.collect(),
|
||||
}
|
||||
},
|
||||
events: state_events,
|
||||
},
|
||||
ephemeral: sync_events::Ephemeral { events: edus },
|
||||
};
|
||||
@ -685,6 +714,7 @@ pub async fn sync_events_route(
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(db))]
|
||||
fn share_encrypted_room(
|
||||
db: &Database,
|
||||
sender_user: &UserId,
|
||||
|
@ -13,6 +13,7 @@ use rocket::{delete, get, put};
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn update_tag_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_tag::Request<'_>>,
|
||||
@ -49,6 +50,7 @@ pub async fn update_tag_route(
|
||||
feature = "conduit_bin",
|
||||
delete("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn delete_tag_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<delete_tag::Request<'_>>,
|
||||
@ -82,6 +84,7 @@ pub async fn delete_tag_route(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/user/<_>/rooms/<_>/tags", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn get_tags_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_tags::Request<'_>>,
|
||||
|
@ -10,6 +10,7 @@ use std::collections::BTreeMap;
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/thirdparty/protocols")
|
||||
)]
|
||||
#[tracing::instrument]
|
||||
pub async fn get_protocols_route() -> ConduitResult<get_protocols::Response> {
|
||||
warn!("TODO: get_protocols_route");
|
||||
Ok(get_protocols::Response {
|
||||
|
@ -12,6 +12,7 @@ use rocket::put;
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/sendToDevice/<_>/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn send_event_to_device_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<send_event_to_device::Request<'_>>,
|
||||
|
@ -10,6 +10,7 @@ use rocket::put;
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/client/r0/rooms/<_>/typing/<_>", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub fn create_typing_event_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_typing_event::Request<'_>>,
|
||||
|
@ -15,6 +15,7 @@ use rocket::get;
|
||||
/// Note: Unstable features are used while developing new features. Clients should avoid using
|
||||
/// unstable features in their stable releases
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/versions"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn get_supported_versions_route() -> ConduitResult<get_supported_versions::Response> {
|
||||
let mut resp =
|
||||
get_supported_versions::Response::new(vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()]);
|
||||
|
@ -9,6 +9,7 @@ use rocket::post;
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/user_directory/search", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub async fn search_users_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<search_users::Request<'_>>,
|
||||
|
@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
use rocket::get;
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/voip/turnServer"))]
|
||||
#[tracing::instrument]
|
||||
pub async fn turn_server_route() -> ConduitResult<get_turn_server_info::Response> {
|
||||
Ok(get_turn_server_info::Response {
|
||||
username: "".to_owned(),
|
||||
|
@ -30,17 +30,22 @@ pub struct Config {
|
||||
server_name: Box<ServerName>,
|
||||
database_path: String,
|
||||
#[serde(default = "default_cache_capacity")]
|
||||
cache_capacity: u64,
|
||||
cache_capacity: u32,
|
||||
#[serde(default = "default_max_request_size")]
|
||||
max_request_size: u32,
|
||||
#[serde(default = "default_max_concurrent_requests")]
|
||||
max_concurrent_requests: u16,
|
||||
#[serde(default)]
|
||||
#[serde(default = "true_fn")]
|
||||
allow_registration: bool,
|
||||
#[serde(default = "true_fn")]
|
||||
allow_encryption: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
allow_federation: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
pub allow_jaeger: bool,
|
||||
jwt_secret: Option<String>,
|
||||
#[serde(default = "Vec::new")]
|
||||
trusted_servers: Vec<Box<ServerName>>,
|
||||
}
|
||||
|
||||
fn false_fn() -> bool {
|
||||
@ -51,7 +56,7 @@ fn true_fn() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_cache_capacity() -> u64 {
|
||||
fn default_cache_capacity() -> u32 {
|
||||
1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
@ -97,8 +102,7 @@ impl Database {
|
||||
pub async fn load_or_create(config: Config) -> Result<Self> {
|
||||
let db = sled::Config::default()
|
||||
.path(&config.database_path)
|
||||
.cache_capacity(config.cache_capacity)
|
||||
.print_profile_on_drop(false)
|
||||
.cache_capacity(config.cache_capacity as u64)
|
||||
.open()?;
|
||||
|
||||
info!("Opened sled database at {}", config.database_path);
|
||||
@ -163,7 +167,8 @@ impl Database {
|
||||
stateid_pduid: db.open_tree("stateid_pduid")?,
|
||||
pduid_statehash: db.open_tree("pduid_statehash")?,
|
||||
roomid_statehash: db.open_tree("roomid_statehash")?,
|
||||
pduid_outlierpdu: db.open_tree("pduid_outlierpdu")?,
|
||||
eventid_outlierpdu: db.open_tree("roomeventid_outlierpdu")?,
|
||||
prevevent_parent: db.open_tree("prevevent_parent")?,
|
||||
},
|
||||
account_data: account_data::AccountData {
|
||||
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
|
||||
|
@ -74,6 +74,7 @@ impl AccountData {
|
||||
}
|
||||
|
||||
/// Returns all changes to the account data that happened after `since`.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn changes_since(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
|
@ -7,7 +7,6 @@ use ruma::{
|
||||
events::{room::message, EventType},
|
||||
UserId,
|
||||
};
|
||||
use tokio::select;
|
||||
|
||||
pub enum AdminCommand {
|
||||
RegisterAppservice(serde_yaml::Value),
|
||||
@ -67,7 +66,7 @@ impl Admin {
|
||||
};
|
||||
|
||||
loop {
|
||||
select! {
|
||||
tokio::select! {
|
||||
Some(event) = receiver.next() => {
|
||||
match event {
|
||||
AdminCommand::RegisterAppservice(yaml) => {
|
||||
|
@ -14,20 +14,26 @@ use trust_dns_resolver::TokioAsyncResolver;
|
||||
pub const COUNTER: &str = "c";
|
||||
|
||||
pub type DestinationCache = Arc<RwLock<HashMap<Box<ServerName>, (String, Option<String>)>>>;
|
||||
type WellKnownMap = HashMap<Box<ServerName>, (String, Option<String>)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Globals {
|
||||
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
|
||||
pub(super) globals: sled::Tree,
|
||||
config: Config,
|
||||
keypair: Arc<ruma::signatures::Ed25519KeyPair>,
|
||||
reqwest_client: reqwest::Client,
|
||||
pub actual_destination_cache: DestinationCache, // actual_destination, host
|
||||
dns_resolver: TokioAsyncResolver,
|
||||
pub(super) servertimeout_signingkey: sled::Tree, // ServerName -> algorithm:key + pubkey
|
||||
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
|
||||
pub(super) servertimeout_signingkey: sled::Tree, // ServerName + Timeout Timestamp -> algorithm:key + pubkey
|
||||
}
|
||||
|
||||
impl Globals {
|
||||
pub fn load(globals: sled::Tree, server_keys: sled::Tree, config: Config) -> Result<Self> {
|
||||
pub fn load(
|
||||
globals: sled::Tree,
|
||||
servertimeout_signingkey: sled::Tree,
|
||||
config: Config,
|
||||
) -> Result<Self> {
|
||||
let bytes = &*globals
|
||||
.update_and_fetch("keypair", utils::generate_keypair)?
|
||||
.expect("utils::generate_keypair always returns Some");
|
||||
@ -69,6 +75,11 @@ impl Globals {
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let jwt_decoding_key = config
|
||||
.jwt_secret
|
||||
.as_ref()
|
||||
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static());
|
||||
|
||||
Ok(Self {
|
||||
globals,
|
||||
config,
|
||||
@ -78,7 +89,8 @@ impl Globals {
|
||||
Error::bad_config("Failed to set up trust dns resolver with system config.")
|
||||
})?,
|
||||
actual_destination_cache: Arc::new(RwLock::new(HashMap::new())),
|
||||
servertimeout_signingkey: server_keys,
|
||||
servertimeout_signingkey,
|
||||
jwt_decoding_key,
|
||||
})
|
||||
}
|
||||
|
||||
@ -129,46 +141,48 @@ impl Globals {
|
||||
self.config.allow_federation
|
||||
}
|
||||
|
||||
pub fn trusted_servers(&self) -> &[Box<ServerName>] {
|
||||
&self.config.trusted_servers
|
||||
}
|
||||
|
||||
pub fn dns_resolver(&self) -> &TokioAsyncResolver {
|
||||
&self.dns_resolver
|
||||
}
|
||||
|
||||
pub fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey<'_>> {
|
||||
self.jwt_decoding_key.as_ref()
|
||||
}
|
||||
|
||||
/// TODO: the key valid until timestamp is only honored in room version > 4
|
||||
/// 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, keys: &ServerSigningKeys) -> Result<()> {
|
||||
// Remove outdated keys
|
||||
let now = crate::utils::millis_since_unix_epoch();
|
||||
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
|
||||
let (k, _) = 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"))?;
|
||||
let mut key1 = origin.as_bytes().to_vec();
|
||||
key1.push(0xff);
|
||||
|
||||
if now > valid_until {
|
||||
self.servertimeout_signingkey.remove(k)?;
|
||||
}
|
||||
}
|
||||
let mut key2 = key1.clone();
|
||||
|
||||
let mut key = origin.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(
|
||||
&(keys
|
||||
.valid_until_ts
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time is valid")
|
||||
.as_millis() as u64)
|
||||
.to_be_bytes(),
|
||||
);
|
||||
let ts = keys
|
||||
.valid_until_ts
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time is valid")
|
||||
.as_millis() as u64;
|
||||
|
||||
key1.extend_from_slice(&ts.to_be_bytes());
|
||||
key2.extend_from_slice(&(ts + 1).to_be_bytes());
|
||||
|
||||
self.servertimeout_signingkey.insert(
|
||||
key,
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -177,7 +191,10 @@ impl Globals {
|
||||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
|
||||
let mut response = BTreeMap::new();
|
||||
|
||||
let now = crate::utils::millis_since_unix_epoch();
|
||||
|
||||
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
|
||||
let (k, bytes) = item?;
|
||||
let valid_until = k
|
||||
@ -188,10 +205,11 @@ impl Globals {
|
||||
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?;
|
||||
// If these keys are still valid use em!
|
||||
if valid_until > now {
|
||||
return serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"));
|
||||
let btree: BTreeMap<_, _> = serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"))?;
|
||||
response.extend(btree);
|
||||
}
|
||||
}
|
||||
Ok(BTreeMap::default())
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::{utils, Error, Result};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::backup::{BackupAlgorithm, KeyData, Sessions},
|
||||
r0::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
|
||||
},
|
||||
RoomId, UserId,
|
||||
};
|
||||
@ -129,7 +129,7 @@ impl KeyBackups {
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
session_id: &str,
|
||||
key_data: &KeyData,
|
||||
key_data: &KeyBackupData,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
@ -153,7 +153,7 @@ impl KeyBackups {
|
||||
|
||||
self.backupkeyid_backup.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&key_data).expect("KeyData::to_string always works"),
|
||||
&*serde_json::to_string(&key_data).expect("KeyBackupData::to_string always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@ -182,13 +182,17 @@ impl KeyBackups {
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn get_all(&self, user_id: &UserId, version: &str) -> Result<BTreeMap<RoomId, Sessions>> {
|
||||
pub fn get_all(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
) -> Result<BTreeMap<RoomId, RoomKeyBackup>> {
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut rooms = BTreeMap::<RoomId, Sessions>::new();
|
||||
let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new();
|
||||
|
||||
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| {
|
||||
let (key, value) = r?;
|
||||
@ -211,15 +215,16 @@ impl KeyBackups {
|
||||
)
|
||||
.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("KeyData 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))
|
||||
}) {
|
||||
let (room_id, session_id, key_data) = result?;
|
||||
rooms
|
||||
.entry(room_id)
|
||||
.or_insert_with(|| Sessions {
|
||||
.or_insert_with(|| RoomKeyBackup {
|
||||
sessions: BTreeMap::new(),
|
||||
})
|
||||
.sessions
|
||||
@ -234,7 +239,7 @@ impl KeyBackups {
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
) -> BTreeMap<String, KeyData> {
|
||||
) -> BTreeMap<String, KeyBackupData> {
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
@ -257,7 +262,7 @@ impl KeyBackups {
|
||||
})?;
|
||||
|
||||
let key_data = serde_json::from_slice(&value).map_err(|_| {
|
||||
Error::bad_database("KeyData in backupkeyid_backup is invalid.")
|
||||
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
|
||||
})?;
|
||||
|
||||
Ok::<_, Error>((session_id, key_data))
|
||||
@ -272,7 +277,7 @@ impl KeyBackups {
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
session_id: &str,
|
||||
) -> Result<Option<KeyData>> {
|
||||
) -> Result<Option<KeyBackupData>> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(version.as_bytes());
|
||||
@ -284,8 +289,9 @@ impl KeyBackups {
|
||||
self.backupkeyid_backup
|
||||
.get(&key)?
|
||||
.map(|value| {
|
||||
serde_json::from_slice(&value)
|
||||
.map_err(|_| Error::bad_database("KeyData in backupkeyid_backup is invalid."))
|
||||
serde_json::from_slice(&value).map_err(|_| {
|
||||
Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use ruma::{
|
||||
},
|
||||
events::room::{
|
||||
member::{MemberEventContent, MembershipState},
|
||||
message::{MessageEventContent, TextMessageEventContent},
|
||||
message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
power_levels::PowerLevelsEventContent,
|
||||
},
|
||||
events::EventType,
|
||||
@ -265,8 +265,8 @@ pub async fn send_push_notice(
|
||||
.map_err(|_| {
|
||||
Error::bad_database("PDU contained bad message content")
|
||||
})?;
|
||||
if let MessageEventContent::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content
|
||||
if let MessageType::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content.msgtype
|
||||
{
|
||||
if body.contains(user.localpart()) {
|
||||
let tweaks = rule
|
||||
@ -305,8 +305,8 @@ pub async fn send_push_notice(
|
||||
.map_err(|_| {
|
||||
Error::bad_database("PDU contained bad message content")
|
||||
})?;
|
||||
if let MessageEventContent::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content
|
||||
if let MessageType::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content.msgtype
|
||||
{
|
||||
let power_level_cmp = |pl: PowerLevelsEventContent| {
|
||||
&pl.notifications.room
|
||||
@ -346,8 +346,8 @@ pub async fn send_push_notice(
|
||||
.map_err(|_| {
|
||||
Error::bad_database("PDU contained bad message content")
|
||||
})?;
|
||||
if let MessageEventContent::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content
|
||||
if let MessageType::Text(TextMessageEventContent { body, .. }) =
|
||||
&msg_content.msgtype
|
||||
{
|
||||
if body.contains(user.localpart()) {
|
||||
let tweaks = rule
|
||||
|
@ -3,7 +3,7 @@ mod edus;
|
||||
pub use edus::RoomEdus;
|
||||
|
||||
use crate::{pdu::PduBuilder, utils, Database, Error, PduEvent, Result};
|
||||
use log::error;
|
||||
use log::{error, info, warn};
|
||||
use regex::Regex;
|
||||
use ring::digest;
|
||||
use ruma::{
|
||||
@ -63,16 +63,24 @@ pub struct Rooms {
|
||||
/// Remember the state hash at events in the past.
|
||||
pub(super) pduid_statehash: sled::Tree,
|
||||
/// The state for a given state hash.
|
||||
pub(super) statekey_short: sled::Tree, // StateKey = EventType + StateKey, Short = Count
|
||||
pub(super) stateid_pduid: sled::Tree, // StateId = StateHash + Short, PduId = Count (without roomid)
|
||||
///
|
||||
/// StateKey = EventType + StateKey, Short = Count
|
||||
pub(super) statekey_short: sled::Tree,
|
||||
/// StateId = StateHash + Short, PduId = Count (without roomid)
|
||||
pub(super) stateid_pduid: sled::Tree,
|
||||
|
||||
/// Any pdu that has passed the steps up to auth with auth_events.
|
||||
pub(super) pduid_outlierpdu: sled::Tree,
|
||||
/// RoomId + EventId -> outlier PDU.
|
||||
/// Any pdu that has passed the steps 1-8 in the incoming event /federation/send/txn.
|
||||
pub(super) eventid_outlierpdu: sled::Tree,
|
||||
|
||||
/// RoomId + EventId -> Parent PDU EventId.
|
||||
pub(super) prevevent_parent: sled::Tree,
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
/// Builds a StateMap by iterating over all keys that start
|
||||
/// with state_hash, this gives the full state for the given state_hash.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn state_full(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -81,16 +89,17 @@ impl Rooms {
|
||||
self.stateid_pduid
|
||||
.scan_prefix(&state_hash)
|
||||
.values()
|
||||
.map(|pduid_short| {
|
||||
let mut pduid = room_id.as_bytes().to_vec();
|
||||
pduid.push(0xff);
|
||||
pduid.extend_from_slice(&pduid_short?);
|
||||
match self.pduid_pdu.get(&pduid)? {
|
||||
.map(|short_id| {
|
||||
let short_id = short_id?;
|
||||
let mut long_id = room_id.as_bytes().to_vec();
|
||||
long_id.push(0xff);
|
||||
long_id.extend_from_slice(&short_id);
|
||||
match self.pduid_pdu.get(&long_id)? {
|
||||
Some(b) => serde_json::from_slice::<PduEvent>(&b)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db.")),
|
||||
None => self
|
||||
.pduid_outlierpdu
|
||||
.get(pduid)?
|
||||
.eventid_outlierpdu
|
||||
.get(short_id)?
|
||||
.map(|b| {
|
||||
serde_json::from_slice::<PduEvent>(&b)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
@ -117,6 +126,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn state_get(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -128,6 +138,8 @@ impl Rooms {
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&state_key.as_bytes());
|
||||
|
||||
info!("Looking for {} {:?}", event_type, state_key);
|
||||
|
||||
let short = self.statekey_short.get(&key)?;
|
||||
|
||||
if let Some(short) = short {
|
||||
@ -135,42 +147,52 @@ impl Rooms {
|
||||
stateid.push(0xff);
|
||||
stateid.extend_from_slice(&short);
|
||||
|
||||
info!("trying to find pduid/eventid. short: {:?}", stateid);
|
||||
self.stateid_pduid
|
||||
.get(&stateid)?
|
||||
.map_or(Ok(None), |pdu_id_short| {
|
||||
let mut pdu_id = room_id.as_bytes().to_vec();
|
||||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&pdu_id_short);
|
||||
.map_or(Ok(None), |short_id| {
|
||||
info!("found in stateid_pduid");
|
||||
let mut long_id = room_id.as_bytes().to_vec();
|
||||
long_id.push(0xff);
|
||||
long_id.extend_from_slice(&short_id);
|
||||
|
||||
Ok::<_, Error>(Some((
|
||||
pdu_id.clone().into(),
|
||||
match self.pduid_pdu.get(&pdu_id)? {
|
||||
Some(b) => serde_json::from_slice::<PduEvent>(&b)
|
||||
Ok::<_, Error>(Some(match self.pduid_pdu.get(&long_id)? {
|
||||
Some(b) => (
|
||||
long_id.clone().into(),
|
||||
serde_json::from_slice::<PduEvent>(&b)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
None => self
|
||||
.pduid_outlierpdu
|
||||
.get(pdu_id)?
|
||||
.map(|b| {
|
||||
serde_json::from_slice::<PduEvent>(&b)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("Event is not in pdu tree or outliers.")
|
||||
})??,
|
||||
},
|
||||
)))
|
||||
),
|
||||
None => {
|
||||
info!("looking in outliers");
|
||||
(
|
||||
short_id.clone().into(),
|
||||
self.eventid_outlierpdu
|
||||
.get(&short_id)?
|
||||
.map(|b| {
|
||||
serde_json::from_slice::<PduEvent>(&b)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("Event is not in pdu tree or outliers.")
|
||||
})??,
|
||||
)
|
||||
}
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
info!("short id not found");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the state hash for this pdu.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn pdu_state_hash(&self, pdu_id: &[u8]) -> Result<Option<StateHashId>> {
|
||||
Ok(self.pduid_statehash.get(pdu_id)?)
|
||||
}
|
||||
|
||||
/// Returns the last state hash key added to the db for the given room.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn current_state_hash(&self, room_id: &RoomId) -> Result<Option<StateHashId>> {
|
||||
Ok(self.roomid_statehash.get(room_id.as_bytes())?)
|
||||
}
|
||||
@ -198,9 +220,11 @@ impl Rooms {
|
||||
&event_type,
|
||||
&state_key
|
||||
.as_deref()
|
||||
.expect("found a non state event in auth events"),
|
||||
.ok_or_else(|| Error::bad_database("Saved auth event with no state key."))?,
|
||||
)? {
|
||||
events.insert((event_type, state_key), pdu);
|
||||
} else {
|
||||
warn!("Could not find {} {:?} in state", event_type, state_key);
|
||||
}
|
||||
}
|
||||
Ok(events)
|
||||
@ -239,11 +263,11 @@ impl Rooms {
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let state_hash =
|
||||
self.calculate_hash(&state.values().map(|pdu_id| &**pdu_id).collect::<Vec<_>>())?;
|
||||
self.calculate_hash(&state.values().map(|long_id| &**long_id).collect::<Vec<_>>())?;
|
||||
let mut prefix = state_hash.to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
for ((event_type, state_key), pdu_id) in state {
|
||||
for ((event_type, state_key), long_id) in state {
|
||||
let mut statekey = event_type.as_ref().as_bytes().to_vec();
|
||||
statekey.push(0xff);
|
||||
statekey.extend_from_slice(&state_key.as_bytes());
|
||||
@ -259,14 +283,13 @@ impl Rooms {
|
||||
}
|
||||
};
|
||||
|
||||
let pdu_id_short = pdu_id
|
||||
.splitn(2, |&b| b == 0xff)
|
||||
.nth(1)
|
||||
.ok_or_else(|| Error::bad_database("Invalid pduid in state."))?;
|
||||
// If it's a pdu id we remove the room id, if it's an event id we leave it the same
|
||||
let short_id = long_id.splitn(2, |&b| b == 0xff).nth(1).unwrap_or(&long_id);
|
||||
|
||||
let mut state_id = prefix.clone();
|
||||
state_id.extend_from_slice(&short.to_be_bytes());
|
||||
self.stateid_pduid.insert(state_id, pdu_id_short)?;
|
||||
info!("inserting {:?} into {:?}", short_id, state_id);
|
||||
self.stateid_pduid.insert(state_id, short_id)?;
|
||||
}
|
||||
|
||||
self.roomid_statehash
|
||||
@ -276,6 +299,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns the full room state.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn room_state_full(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -288,6 +312,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn room_state_get(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -302,6 +327,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn pdu_count(&self, pdu_id: &[u8]) -> Result<u64> {
|
||||
Ok(
|
||||
utils::u64_from_bytes(&pdu_id[pdu_id.len() - mem::size_of::<u64>()..pdu_id.len()])
|
||||
@ -316,21 +342,32 @@ impl Rooms {
|
||||
.map_or(Ok(None), |pdu_id| self.pdu_count(&pdu_id).map(Some))
|
||||
}
|
||||
|
||||
pub fn latest_pdu_count(&self, room_id: &RoomId) -> Result<u64> {
|
||||
self.pduid_pdu
|
||||
.scan_prefix(room_id.as_bytes())
|
||||
.last()
|
||||
.map(|b| self.pdu_count(&b?.0))
|
||||
.transpose()
|
||||
.map(|op| op.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
pub fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<serde_json::Value>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or(Ok(None), |pdu_id| {
|
||||
Ok(Some(
|
||||
serde_json::from_slice(&match self.pduid_pdu.get(&pdu_id)? {
|
||||
Some(b) => b,
|
||||
None => self.pduid_outlierpdu.get(pdu_id)?.ok_or_else(|| {
|
||||
Error::bad_database("Event is not in pdu tree or outliers.")
|
||||
})?,
|
||||
})
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
))
|
||||
.map_or_else::<Result<_>, _, _>(
|
||||
|| Ok(self.eventid_outlierpdu.get(event_id.as_bytes())?),
|
||||
|pduid| {
|
||||
Ok(Some(self.pduid_pdu.get(&pduid)?.ok_or_else(|| {
|
||||
Error::bad_database("Invalid pduid in eventid_pduid.")
|
||||
})?))
|
||||
},
|
||||
)?
|
||||
.map(|pdu| {
|
||||
Ok(serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the pdu's id.
|
||||
@ -340,24 +377,36 @@ impl Rooms {
|
||||
.map_or(Ok(None), |pdu_id| Ok(Some(pdu_id)))
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
pub fn get_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or(Ok(None), |pdu_id| {
|
||||
Ok(Some(
|
||||
serde_json::from_slice(&match self.pduid_pdu.get(&pdu_id)? {
|
||||
Some(b) => b,
|
||||
None => self.pduid_outlierpdu.get(pdu_id)?.ok_or_else(|| {
|
||||
Error::bad_database("Event is not in pdu tree or outliers.")
|
||||
})?,
|
||||
})
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
))
|
||||
})
|
||||
pub fn get_long_id(&self, event_id: &EventId) -> Result<Vec<u8>> {
|
||||
Ok(self
|
||||
.get_pdu_id(event_id)?
|
||||
.map_or_else(|| event_id.as_bytes().to_vec(), |pduid| pduid.to_vec()))
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
|
||||
pub fn get_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or_else::<Result<_>, _, _>(
|
||||
|| Ok(self.eventid_outlierpdu.get(event_id.as_bytes())?),
|
||||
|pduid| {
|
||||
Ok(Some(self.pduid_pdu.get(&pduid)?.ok_or_else(|| {
|
||||
Error::bad_database("Invalid pduid in eventid_pduid.")
|
||||
})?))
|
||||
},
|
||||
)?
|
||||
.map(|pdu| {
|
||||
Ok(serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// This does __NOT__ check the outliers `Tree`.
|
||||
pub fn get_pdu_from_id(&self, pdu_id: &IVec) -> Result<Option<PduEvent>> {
|
||||
self.pduid_pdu.get(pdu_id)?.map_or(Ok(None), |pdu| {
|
||||
Ok(Some(
|
||||
@ -421,7 +470,7 @@ impl Rooms {
|
||||
|
||||
/// Replace the leaves of a room.
|
||||
///
|
||||
/// The provided `event_ids` become the new leaves, this enables an event having multiple
|
||||
/// The provided `event_ids` become the new leaves, this allows a room to have multiple
|
||||
/// `prev_events`.
|
||||
pub fn replace_pdu_leaves(&self, room_id: &RoomId, event_ids: &[EventId]) -> Result<()> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
@ -440,39 +489,38 @@ impl Rooms {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the pdu from the outlier tree.
|
||||
pub fn get_pdu_outlier(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
if let Some(id) = self.eventid_pduid.get(event_id.as_bytes())? {
|
||||
self.pduid_outlierpdu.get(id)?.map_or(Ok(None), |pdu| {
|
||||
serde_json::from_slice(&pdu).map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
pub fn is_pdu_referenced(&self, pdu: &PduEvent) -> Result<bool> {
|
||||
let mut key = pdu.room_id().as_bytes().to_vec();
|
||||
key.extend_from_slice(pdu.event_id().as_bytes());
|
||||
self.prevevent_parent.contains_key(key).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns true if the event_id was previously inserted.
|
||||
pub fn append_pdu_outlier(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<bool> {
|
||||
log::info!("Number of outlier pdu's {}", self.pduid_outlierpdu.len());
|
||||
/// Returns the pdu from the outlier tree.
|
||||
pub fn get_pdu_outlier(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
self.eventid_outlierpdu
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or(Ok(None), |pdu| {
|
||||
serde_json::from_slice(&pdu).map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
}
|
||||
|
||||
// we need to be able to find it by event_id
|
||||
self.eventid_pduid
|
||||
.insert(pdu.event_id.as_bytes(), &*pdu_id)?;
|
||||
/// Append the PDU as an outlier.
|
||||
///
|
||||
/// Any event given to this will be processed (state-res) on another thread.
|
||||
pub fn add_pdu_outlier(&self, pdu: &PduEvent) -> Result<()> {
|
||||
self.eventid_outlierpdu.insert(
|
||||
&pdu.event_id.as_bytes(),
|
||||
&*serde_json::to_string(&pdu).expect("PduEvent is always a valid String"),
|
||||
)?;
|
||||
|
||||
let res = self
|
||||
.pduid_outlierpdu
|
||||
.insert(
|
||||
pdu_id,
|
||||
&*serde_json::to_string(&pdu).expect("PduEvent is always a valid String"),
|
||||
)
|
||||
.map(|op| op.is_some())?;
|
||||
Ok(res)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new persisted data unit and adds it to a room.
|
||||
///
|
||||
/// By this point the incoming event should be fully authenticated, no auth happens
|
||||
/// in `append_pdu`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn append_pdu(
|
||||
&self,
|
||||
pdu: &PduEvent,
|
||||
@ -509,9 +557,12 @@ impl Rooms {
|
||||
}
|
||||
}
|
||||
|
||||
// We no longer keep this pdu as an outlier
|
||||
if let Some(id) = self.eventid_pduid.remove(pdu.event_id().as_bytes())? {
|
||||
self.pduid_outlierpdu.remove(id)?;
|
||||
// We must keep track of all events that have been referenced.
|
||||
for leaf in leaves {
|
||||
let mut key = pdu.room_id().as_bytes().to_vec();
|
||||
key.extend_from_slice(leaf.as_bytes());
|
||||
self.prevevent_parent
|
||||
.insert(key, pdu.event_id().as_bytes())?;
|
||||
}
|
||||
|
||||
self.replace_pdu_leaves(&pdu.room_id, leaves)?;
|
||||
@ -527,6 +578,8 @@ impl Rooms {
|
||||
.expect("CanonicalJsonObject is always a valid String"),
|
||||
)?;
|
||||
|
||||
// This also replaces the eventid of any outliers with the correct
|
||||
// pduid, removing the place holder.
|
||||
self.eventid_pduid
|
||||
.insert(pdu.event_id.as_bytes(), &*pdu_id)?;
|
||||
|
||||
@ -836,12 +889,12 @@ impl Rooms {
|
||||
content.clone(),
|
||||
prev_event,
|
||||
None, // TODO: third party invite
|
||||
&auth_events
|
||||
dbg!(&auth_events
|
||||
.iter()
|
||||
.map(|((ty, key), pdu)| {
|
||||
Ok(((ty.clone(), key.clone()), Arc::new(pdu.clone())))
|
||||
})
|
||||
.collect::<Result<StateMap<_>>>()?,
|
||||
.collect::<Result<StateMap<_>>>()?),
|
||||
)
|
||||
.map_err(|e| {
|
||||
log::error!("{}", e);
|
||||
@ -1025,6 +1078,7 @@ impl Rooms {
|
||||
|
||||
let user_is_joined =
|
||||
|bridge_user_id| self.is_joined(&bridge_user_id, room_id).unwrap_or(false);
|
||||
|
||||
let matching_users = |users: &Regex| {
|
||||
users.is_match(pdu.sender.as_str())
|
||||
|| pdu.kind == EventType::RoomMember
|
||||
@ -1053,6 +1107,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all PDUs in a room.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn all_pdus(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@ -1063,6 +1118,7 @@ impl Rooms {
|
||||
|
||||
/// Returns a double-ended iterator over all events in a room that happened after the event with id `since`
|
||||
/// in chronological order.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn pdus_since(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@ -1129,6 +1185,7 @@ impl Rooms {
|
||||
|
||||
/// Returns an iterator over all events and their token in a room that happened after the event
|
||||
/// with id `from` in chronological order.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn pdus_after(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@ -1177,7 +1234,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Update current membership data.
|
||||
fn update_membership(
|
||||
pub fn update_membership(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
@ -1482,6 +1539,7 @@ impl Rooms {
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_shared_rooms<'a>(
|
||||
&'a self,
|
||||
users: Vec<UserId>,
|
||||
@ -1521,7 +1579,7 @@ impl Rooms {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all joined members of a room.
|
||||
/// Returns an iterator of all servers participating in this room.
|
||||
pub fn room_servers(&self, room_id: &RoomId) -> impl Iterator<Item = Result<Box<ServerName>>> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
@ -1543,6 +1601,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all joined members of a room.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn room_members(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
@ -1591,6 +1650,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all invited members of a room.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
@ -1615,6 +1675,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms this user joined.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn rooms_joined(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
self.userroomid_joined
|
||||
.scan_prefix(user_id.as_bytes())
|
||||
@ -1636,6 +1697,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user was invited to.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn rooms_invited(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
@ -1660,6 +1722,7 @@ impl Rooms {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user left.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn rooms_left(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
@ -70,6 +70,7 @@ 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(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -115,6 +116,7 @@ impl RoomEdus {
|
||||
}
|
||||
|
||||
/// Returns the private read marker.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn private_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>> {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
@ -256,6 +258,7 @@ impl RoomEdus {
|
||||
}
|
||||
|
||||
/// Returns the count of the last typing update in this room.
|
||||
#[tracing::instrument(skip(self, globals))]
|
||||
pub fn last_typing_update(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -339,6 +342,7 @@ impl RoomEdus {
|
||||
}
|
||||
|
||||
/// Resets the presence timeout, so the user will stay in their current presence state.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn ping_presence(&self, user_id: &UserId) -> Result<()> {
|
||||
self.userid_lastpresenceupdate.insert(
|
||||
&user_id.to_string().as_bytes(),
|
||||
@ -429,6 +433,7 @@ impl RoomEdus {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the most recent presence updates that happened after the event with id `since`.
|
||||
#[tracing::instrument(skip(self, globals, rooms))]
|
||||
pub fn presence_since(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
|
@ -6,9 +6,12 @@ use std::{
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
|
||||
use crate::{appservice_server, server_server, utils, Database, Error, PduEvent, Result};
|
||||
use crate::{
|
||||
appservice_server, database::pusher, server_server, utils, Database, Error, PduEvent, Result,
|
||||
};
|
||||
use federation::transactions::send_transaction_message;
|
||||
use log::info;
|
||||
use log::{info, warn};
|
||||
use ring::digest;
|
||||
use rocket::futures::stream::{FuturesUnordered, StreamExt};
|
||||
use ruma::{
|
||||
api::{appservice, federation, OutgoingRequest},
|
||||
@ -37,30 +40,66 @@ impl Sending {
|
||||
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 current_transactions = HashMap::new();
|
||||
let mut current_transactions = HashMap::<OutgoingKind, Vec<IVec>>::new();
|
||||
|
||||
for (outgoing_kind, pdu) in servercurrentpdus
|
||||
for (key, outgoing_kind, pdu) in servercurrentpdus
|
||||
.iter()
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|(key, _)| Self::parse_servercurrentpdus(key).ok())
|
||||
.filter(|(_, pdu)| !pdu.is_empty()) // Skip reservation key
|
||||
.take(50)
|
||||
// This should not contain more than 50 anyway
|
||||
.filter_map(|(key, _)| {
|
||||
Self::parse_servercurrentpdus(&key)
|
||||
.ok()
|
||||
.map(|(k, p)| (key, k, p))
|
||||
})
|
||||
{
|
||||
current_transactions
|
||||
if pdu.is_empty() {
|
||||
// Remove old reservation key
|
||||
servercurrentpdus.remove(key).unwrap();
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = current_transactions
|
||||
.entry(outgoing_kind)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pdu);
|
||||
.or_insert_with(Vec::new);
|
||||
|
||||
if entry.len() > 30 {
|
||||
warn!("Dropping some current pdus because too many were queued. This should not happen.");
|
||||
servercurrentpdus.remove(key).unwrap();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.push(pdu);
|
||||
}
|
||||
|
||||
for (outgoing_kind, pdus) in current_transactions {
|
||||
futures.push(Self::handle_event(outgoing_kind, pdus, &db));
|
||||
// Create new reservation
|
||||
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(id) => {
|
||||
let mut p = b"$".to_vec();
|
||||
p.extend_from_slice(&id);
|
||||
p
|
||||
}
|
||||
OutgoingKind::Normal(server) => {
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(server.as_bytes());
|
||||
p
|
||||
}
|
||||
};
|
||||
prefix.push(0xff);
|
||||
servercurrentpdus.insert(prefix, &[]).unwrap();
|
||||
|
||||
futures.push(Self::handle_event(outgoing_kind.clone(), pdus, &db));
|
||||
}
|
||||
|
||||
let mut last_failed_try: HashMap<OutgoingKind, (u32, Instant)> = HashMap::new();
|
||||
@ -109,7 +148,7 @@ impl Sending {
|
||||
.map(|k| {
|
||||
k.subslice(prefix.len(), k.len() - prefix.len())
|
||||
})
|
||||
.take(50)
|
||||
.take(30)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !new_pdus.is_empty() {
|
||||
@ -255,6 +294,7 @@ impl Sending {
|
||||
});
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn send_push_pdu(&self, pdu_id: &[u8]) -> Result<()> {
|
||||
// Make sure we don't cause utf8 errors when parsing to a String...
|
||||
let pduid = String::from_utf8_lossy(pdu_id).as_bytes().to_vec();
|
||||
@ -273,6 +313,7 @@ impl Sending {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn send_pdu(&self, server: &ServerName, pdu_id: &[u8]) -> Result<()> {
|
||||
let mut key = server.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
@ -282,6 +323,7 @@ impl Sending {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn send_pdu_appservice(&self, appservice_id: &str, pdu_id: &[u8]) -> Result<()> {
|
||||
let mut key = b"+".to_vec();
|
||||
key.extend_from_slice(appservice_id.as_bytes());
|
||||
@ -292,13 +334,21 @@ impl Sending {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO this is the whole DB but is it better to clone smaller parts than the whole thing??
|
||||
#[tracing::instrument]
|
||||
fn calculate_hash(keys: &[IVec]) -> 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);
|
||||
hash.as_ref().to_owned()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(db))]
|
||||
async fn handle_event(
|
||||
kind: OutgoingKind,
|
||||
pdu_ids: Vec<IVec>,
|
||||
db: &Database,
|
||||
) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> {
|
||||
match dbg!(kind) {
|
||||
match dbg!(&kind) {
|
||||
OutgoingKind::Appservice(server) => {
|
||||
let pdu_jsons = pdu_ids
|
||||
.iter()
|
||||
@ -320,7 +370,8 @@ impl Sending {
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<_>>();
|
||||
appservice_server::send_request(
|
||||
let permit = db.sending.maximum_requests.acquire().await;
|
||||
let response = appservice_server::send_request(
|
||||
&db.globals,
|
||||
db.appservice
|
||||
.get_registration(server.as_str())
|
||||
@ -328,12 +379,19 @@ impl Sending {
|
||||
.unwrap(), // TODO: handle error
|
||||
appservice::event::push_events::v1::Request {
|
||||
events: &pdu_jsons,
|
||||
txn_id: &utils::random_string(16),
|
||||
txn_id: &base64::encode_config(
|
||||
Self::calculate_hash(&pdu_ids),
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_response| OutgoingKind::Appservice(server.clone()))
|
||||
.map_err(|e| (OutgoingKind::Appservice(server.clone()), e))
|
||||
.map(|_response| kind.clone())
|
||||
.map_err(|e| (kind, e));
|
||||
|
||||
drop(permit);
|
||||
|
||||
response
|
||||
}
|
||||
OutgoingKind::Push(id) => {
|
||||
let pdus = pdu_ids
|
||||
@ -403,22 +461,23 @@ impl Sending {
|
||||
uint!(0)
|
||||
};
|
||||
|
||||
dbg!(
|
||||
crate::database::pusher::send_push_notice(
|
||||
&user,
|
||||
unread,
|
||||
&pushers,
|
||||
rules_for_user,
|
||||
pdu,
|
||||
db,
|
||||
)
|
||||
.await
|
||||
let permit = db.sending.maximum_requests.acquire().await;
|
||||
let _response = pusher::send_push_notice(
|
||||
&user,
|
||||
unread,
|
||||
&pushers,
|
||||
rules_for_user,
|
||||
pdu,
|
||||
db,
|
||||
)
|
||||
.map_err(|e| (OutgoingKind::Push(id.clone()), e))?;
|
||||
.await
|
||||
.map(|_response| kind.clone())
|
||||
.map_err(|e| (kind.clone(), e));
|
||||
|
||||
drop(permit);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OutgoingKind::Push(id))
|
||||
Ok(OutgoingKind::Push(id.clone()))
|
||||
}
|
||||
OutgoingKind::Normal(server) => {
|
||||
let pdu_jsons = pdu_ids
|
||||
@ -449,7 +508,10 @@ impl Sending {
|
||||
.filter_map(|r| r.ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
server_server::send_request(
|
||||
let permit = db.sending.maximum_requests.acquire().await;
|
||||
|
||||
info!("sending pdus to {}: {:#?}", server, pdu_jsons);
|
||||
let response = server_server::send_request(
|
||||
&db.globals,
|
||||
&*server,
|
||||
send_transaction_message::v1::Request {
|
||||
@ -457,17 +519,27 @@ impl Sending {
|
||||
pdus: &pdu_jsons,
|
||||
edus: &[],
|
||||
origin_server_ts: SystemTime::now(),
|
||||
transaction_id: &utils::random_string(16),
|
||||
transaction_id: &base64::encode_config(
|
||||
Self::calculate_hash(&pdu_ids),
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_response| OutgoingKind::Normal(server.clone()))
|
||||
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))
|
||||
.map(|response| {
|
||||
info!("server response: {:?}", response);
|
||||
kind.clone()
|
||||
})
|
||||
.map_err(|e| (kind, e));
|
||||
|
||||
drop(permit);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_servercurrentpdus(key: IVec) -> Result<(OutgoingKind, IVec)> {
|
||||
fn parse_servercurrentpdus(key: &IVec) -> Result<(OutgoingKind, IVec)> {
|
||||
let mut parts = key.splitn(2, |&b| b == 0xff);
|
||||
let server = parts.next().expect("splitn always returns one element");
|
||||
let pdu = parts
|
||||
@ -501,6 +573,7 @@ impl Sending {
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, globals))]
|
||||
pub async fn send_federation_request<T: OutgoingRequest>(
|
||||
&self,
|
||||
globals: &crate::database::globals::Globals,
|
||||
@ -517,6 +590,7 @@ impl Sending {
|
||||
response
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, globals))]
|
||||
pub async fn send_appservice_request<T: OutgoingRequest>(
|
||||
&self,
|
||||
globals: &crate::database::globals::Globals,
|
||||
|
@ -251,7 +251,7 @@ impl Users {
|
||||
}
|
||||
|
||||
/// Replaces the access token of one device.
|
||||
fn set_token(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> {
|
||||
pub fn set_token(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> {
|
||||
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
|
||||
userdeviceid.push(0xff);
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
@ -311,6 +311,7 @@ impl Users {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn last_one_time_keys_update(&self, user_id: &UserId) -> Result<u64> {
|
||||
self.userid_lastonetimekeyupdate
|
||||
.get(&user_id.to_string().as_bytes())?
|
||||
@ -364,6 +365,7 @@ impl Users {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn count_one_time_keys(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@ -563,6 +565,7 @@ impl Users {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn keys_changed(
|
||||
&self,
|
||||
user_or_room_id: &str,
|
||||
@ -738,6 +741,7 @@ impl Users {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_to_device_events(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@ -760,6 +764,7 @@ impl Users {
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn remove_to_device_events(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
|
88
src/main.rs
88
src/main.rs
@ -11,10 +11,12 @@ mod push_rules;
|
||||
mod ruma_wrapper;
|
||||
mod utils;
|
||||
|
||||
use database::Config;
|
||||
pub use database::Database;
|
||||
pub use error::{ConduitLogger, Error, Result};
|
||||
pub use error::{Error, Result};
|
||||
pub use pdu::PduEvent;
|
||||
pub use rocket::State;
|
||||
use ruma::api::client::error::ErrorKind;
|
||||
pub use ruma_wrapper::{ConduitResult, Ruma, RumaResponse};
|
||||
|
||||
use log::LevelFilter;
|
||||
@ -27,8 +29,10 @@ use rocket::{
|
||||
},
|
||||
routes, Request,
|
||||
};
|
||||
use tracing::span;
|
||||
use tracing_subscriber::{prelude::*, Registry};
|
||||
|
||||
fn setup_rocket() -> rocket::Rocket {
|
||||
fn setup_rocket() -> (rocket::Rocket, Config) {
|
||||
// Force log level off, so we can use our own logger
|
||||
std::env::set_var("CONDUIT_LOG_LEVEL", "off");
|
||||
|
||||
@ -42,7 +46,12 @@ fn setup_rocket() -> rocket::Rocket {
|
||||
)
|
||||
.merge(Env::prefixed("CONDUIT_").global());
|
||||
|
||||
rocket::custom(config)
|
||||
let parsed_config = config
|
||||
.extract::<Config>()
|
||||
.expect("It looks like your config is invalid. Please take a look at the error");
|
||||
let parsed_config2 = parsed_config.clone();
|
||||
|
||||
let rocket = rocket::custom(config)
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
@ -93,7 +102,7 @@ fn setup_rocket() -> rocket::Rocket {
|
||||
client_server::get_backup_key_sessions_route,
|
||||
client_server::get_backup_keys_route,
|
||||
client_server::set_read_marker_route,
|
||||
client_server::set_receipt_route,
|
||||
client_server::create_receipt_route,
|
||||
client_server::create_typing_event_route,
|
||||
client_server::create_room_route,
|
||||
client_server::redact_event_route,
|
||||
@ -159,35 +168,76 @@ fn setup_rocket() -> rocket::Rocket {
|
||||
server_server::get_profile_information_route,
|
||||
],
|
||||
)
|
||||
.register(catchers![not_found_catcher])
|
||||
.register(catchers![
|
||||
not_found_catcher,
|
||||
forbidden_catcher,
|
||||
unknown_token_catcher,
|
||||
missing_token_catcher,
|
||||
bad_json_catcher
|
||||
])
|
||||
.attach(AdHoc::on_attach("Config", |rocket| async {
|
||||
let config = rocket
|
||||
.figment()
|
||||
.extract()
|
||||
.expect("It looks like your config is invalid. Please take a look at the error");
|
||||
|
||||
let data = Database::load_or_create(config)
|
||||
let data = Database::load_or_create(parsed_config2)
|
||||
.await
|
||||
.expect("config is valid");
|
||||
|
||||
data.sending.start_handler(&data);
|
||||
log::set_boxed_logger(Box::new(ConduitLogger {
|
||||
db: data.clone(),
|
||||
last_logs: Default::default(),
|
||||
}))
|
||||
.unwrap();
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
|
||||
Ok(rocket.manage(data))
|
||||
}))
|
||||
}));
|
||||
|
||||
(rocket, parsed_config)
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() {
|
||||
setup_rocket().launch().await.unwrap();
|
||||
let (rocket, config) = setup_rocket();
|
||||
|
||||
if config.allow_jaeger {
|
||||
let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline()
|
||||
.with_service_name("conduit")
|
||||
.install()
|
||||
.unwrap();
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
Registry::default().with(telemetry).try_init().unwrap();
|
||||
|
||||
let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
|
||||
let _enter = root.enter();
|
||||
|
||||
rocket.launch().await.unwrap();
|
||||
} else {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
|
||||
let _enter = root.enter();
|
||||
|
||||
rocket.launch().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
fn not_found_catcher(_: &Request<'_>) -> String {
|
||||
"404 Not Found".to_owned()
|
||||
}
|
||||
|
||||
#[catch(580)]
|
||||
fn forbidden_catcher() -> Result<()> {
|
||||
Err(Error::BadRequest(ErrorKind::Forbidden, "Forbidden."))
|
||||
}
|
||||
|
||||
#[catch(581)]
|
||||
fn unknown_token_catcher() -> Result<()> {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown token.",
|
||||
))
|
||||
}
|
||||
|
||||
#[catch(582)]
|
||||
fn missing_token_catcher() -> Result<()> {
|
||||
Err(Error::BadRequest(ErrorKind::MissingToken, "Missing token."))
|
||||
}
|
||||
|
||||
#[catch(583)]
|
||||
fn bad_json_catcher() -> Result<()> {
|
||||
Err(Error::BadRequest(ErrorKind::BadJson, "Bad json."))
|
||||
}
|
||||
|
38
src/pdu.rs
38
src/pdu.rs
@ -4,7 +4,7 @@ use ruma::{
|
||||
pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent,
|
||||
AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, StateEvent,
|
||||
},
|
||||
serde::{CanonicalJsonObject, CanonicalJsonValue, Raw},
|
||||
serde::{to_canonical_value, CanonicalJsonObject, CanonicalJsonValue, Raw},
|
||||
EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UInt, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -34,6 +34,7 @@ pub struct PduEvent {
|
||||
}
|
||||
|
||||
impl PduEvent {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn redact(&mut self, reason: &PduEvent) -> crate::Result<()> {
|
||||
self.unsigned.clear();
|
||||
|
||||
@ -80,6 +81,7 @@ impl PduEvent {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_sync_room_event(&self) -> Raw<AnySyncRoomEvent> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
@ -101,6 +103,7 @@ impl PduEvent {
|
||||
}
|
||||
|
||||
/// This only works for events that are also AnyRoomEvents.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_any_event(&self) -> Raw<AnyEvent> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
@ -122,6 +125,7 @@ impl PduEvent {
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_room_event(&self) -> Raw<AnyRoomEvent> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
@ -143,6 +147,7 @@ impl PduEvent {
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
@ -158,20 +163,27 @@ impl PduEvent {
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
"state_key": self.state_key,
|
||||
});
|
||||
let json = format!(
|
||||
r#"{{"content":{},"type":"{}","event_id":"{}","sender":"{}","origin_server_ts":{},"unsigned":{},"state_key":"{}"}}"#,
|
||||
self.content,
|
||||
self.kind,
|
||||
self.event_id,
|
||||
self.sender,
|
||||
self.origin_server_ts,
|
||||
serde_json::to_string(&self.unsigned).expect("Map::to_string always works"),
|
||||
self.state_key
|
||||
.as_ref()
|
||||
.expect("state events have state keys")
|
||||
);
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
Raw::from_json(
|
||||
serde_json::value::RawValue::from_string(json).expect("our string is valid json"),
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
@ -183,6 +195,7 @@ impl PduEvent {
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_member_event(&self) -> Raw<StateEvent<MemberEventContent>> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
@ -200,6 +213,7 @@ impl PduEvent {
|
||||
}
|
||||
|
||||
/// This does not return a full `Pdu` it is only to satisfy ruma's types.
|
||||
#[tracing::instrument]
|
||||
pub fn convert_to_outgoing_federation_event(
|
||||
mut pdu_json: CanonicalJsonObject,
|
||||
) -> Raw<ruma::events::pdu::Pdu> {
|
||||
@ -228,7 +242,7 @@ impl PduEvent {
|
||||
) -> Result<Self, serde_json::Error> {
|
||||
json.insert(
|
||||
"event_id".to_string(),
|
||||
ruma::serde::to_canonical_value(event_id).expect("event_id is a valid Value"),
|
||||
to_canonical_value(event_id).expect("event_id is a valid Value"),
|
||||
);
|
||||
|
||||
serde_json::from_value(serde_json::to_value(json).expect("valid JSON"))
|
||||
|
@ -42,7 +42,7 @@ impl<'a, T: Outgoing + OutgoingRequest> FromTransformedData<'a> for Ruma<T>
|
||||
where
|
||||
T::Incoming: IncomingRequest,
|
||||
{
|
||||
type Error = (); // TODO: Better error handling
|
||||
type Error = ();
|
||||
type Owned = Data;
|
||||
type Borrowed = Self::Owned;
|
||||
|
||||
@ -102,7 +102,8 @@ where
|
||||
);
|
||||
|
||||
if !db.users.exists(&user_id).unwrap() {
|
||||
return Failure((Status::Unauthorized, ()));
|
||||
// Forbidden
|
||||
return Failure((Status::raw(580), ()));
|
||||
}
|
||||
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
@ -116,15 +117,15 @@ where
|
||||
AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => {
|
||||
if let Some(token) = token {
|
||||
match db.users.find_from_token(&token).unwrap() {
|
||||
// TODO: M_UNKNOWN_TOKEN
|
||||
None => return Failure((Status::Unauthorized, ())),
|
||||
// Unknown Token
|
||||
None => return Failure((Status::raw(581), ())),
|
||||
Some((user_id, device_id)) => {
|
||||
(Some(user_id), Some(device_id.into()), false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: M_MISSING_TOKEN
|
||||
return Failure((Status::Unauthorized, ()));
|
||||
// Missing Token
|
||||
return Failure((Status::raw(582), ()));
|
||||
}
|
||||
}
|
||||
AuthScheme::ServerSignatures => (None, None, false),
|
||||
@ -159,7 +160,7 @@ where
|
||||
}),
|
||||
Err(e) => {
|
||||
warn!("{:?}", e);
|
||||
Failure((Status::BadRequest, ()))
|
||||
Failure((Status::raw(583), ()))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -55,6 +55,7 @@ pub fn random_string(length: usize) -> String {
|
||||
thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(length)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ ARG SCCACHE_ENDPOINT
|
||||
ARG SCCACHE_S3_USE_SSL
|
||||
|
||||
COPY . .
|
||||
RUN cargo build
|
||||
RUN test -e cached_target/release/conduit || cargo build --release
|
||||
|
||||
FROM valkum/docker-rust-ci:latest
|
||||
WORKDIR /workdir
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ import sys
|
||||
# The main complexity is grouping tests sensibly into features like 'Registration'
|
||||
# and 'Federation'. Then it just checks the ones which are passing and calculates
|
||||
# percentages for each group. Produces results like:
|
||||
#
|
||||
#
|
||||
# Client-Server APIs: 29% (196/666 tests)
|
||||
# -------------------
|
||||
# Registration : 62% (20/32 tests)
|
||||
@ -28,11 +28,13 @@ import sys
|
||||
# ✓ POST /register can create a user
|
||||
# ✓ POST /register downcases capitals in usernames
|
||||
# ...
|
||||
#
|
||||
#
|
||||
# You can also tack `-v` on to see exactly which tests each category falls under.
|
||||
|
||||
test_mappings = {
|
||||
"nsp": "Non-Spec API",
|
||||
"unk": "Unknown API (no group specified)",
|
||||
"app": "Application Services API",
|
||||
"f": "Federation", # flag to mark test involves federation
|
||||
|
||||
"federation_apis": {
|
||||
@ -50,6 +52,7 @@ test_mappings = {
|
||||
"fpb": "Public Room API",
|
||||
"fdk": "Device Key APIs",
|
||||
"fed": "Federation API",
|
||||
"fsd": "Send-to-Device APIs",
|
||||
},
|
||||
|
||||
"client_apis": {
|
||||
@ -61,6 +64,8 @@ test_mappings = {
|
||||
"pro": "Profile",
|
||||
"dev": "Devices",
|
||||
"dvk": "Device Keys",
|
||||
"dkb": "Device Key Backup",
|
||||
"xsk": "Cross-signing Keys",
|
||||
"pre": "Presence",
|
||||
"crm": "Create Room",
|
||||
"syn": "Sync API",
|
||||
@ -98,7 +103,7 @@ test_mappings = {
|
||||
"adm": "Server Admin API",
|
||||
"ign": "Ignore Users",
|
||||
"udr": "User Directory APIs",
|
||||
"app": "Application Services API",
|
||||
"jso": "Enforced canonical JSON",
|
||||
},
|
||||
}
|
||||
|
||||
@ -156,20 +161,22 @@ def print_stats(header_name, gid_to_tests, gid_to_name, verbose):
|
||||
total_tests = 0
|
||||
for gid, tests in gid_to_tests.items():
|
||||
group_total = len(tests)
|
||||
if group_total == 0:
|
||||
continue
|
||||
group_passing = 0
|
||||
test_names_and_marks = []
|
||||
for name, passing in tests.items():
|
||||
if passing:
|
||||
group_passing += 1
|
||||
test_names_and_marks.append(f"{'✓' if passing else '×'} {name}")
|
||||
|
||||
|
||||
total_tests += group_total
|
||||
total_passing += group_passing
|
||||
pct = "{0:.0f}%".format(group_passing/group_total * 100)
|
||||
line = "%s: %s (%d/%d tests)" % (gid_to_name[gid].ljust(25, ' '), pct.rjust(4, ' '), group_passing, group_total)
|
||||
subsections.append(line)
|
||||
subsection_test_names[line] = test_names_and_marks
|
||||
|
||||
|
||||
pct = "{0:.0f}%".format(total_passing/total_tests * 100)
|
||||
print("%s: %s (%d/%d tests)" % (header_name, pct, total_passing, total_tests))
|
||||
print("-" * (len(header_name)+1))
|
||||
@ -186,7 +193,6 @@ def main(results_tap_path, verbose):
|
||||
test_name_to_group_id = {}
|
||||
fed_tests = set()
|
||||
client_tests = set()
|
||||
groupless_tests = set()
|
||||
with open("./are-we-synapse-yet.list", "r") as f:
|
||||
for line in f.readlines():
|
||||
test_name = " ".join(line.split(" ")[1:]).strip()
|
||||
@ -212,8 +218,12 @@ def main(results_tap_path, verbose):
|
||||
# test_name: OK
|
||||
# }
|
||||
},
|
||||
"appservice": {
|
||||
"app": {},
|
||||
},
|
||||
"nonspec": {
|
||||
"nsp": {}
|
||||
"nsp": {},
|
||||
"unk": {}
|
||||
},
|
||||
}
|
||||
with open(results_tap_path, "r") as f:
|
||||
@ -224,10 +234,11 @@ def main(results_tap_path, verbose):
|
||||
name = test_result["name"]
|
||||
group_id = test_name_to_group_id.get(name)
|
||||
if not group_id:
|
||||
groupless_tests.add(name)
|
||||
# raise Exception("The test '%s' doesn't have a group" % (name,))
|
||||
summary["nonspec"]["unk"][name] = test_result["ok"]
|
||||
if group_id == "nsp":
|
||||
summary["nonspec"]["nsp"][name] = test_result["ok"]
|
||||
elif group_id == "app":
|
||||
summary["appservice"]["app"][name] = test_result["ok"]
|
||||
elif group_id in test_mappings["federation_apis"]:
|
||||
group = summary["federation"].get(group_id, {})
|
||||
group[name] = test_result["ok"]
|
||||
@ -243,12 +254,7 @@ def main(results_tap_path, verbose):
|
||||
print_stats("Non-Spec APIs", summary["nonspec"], test_mappings, verbose)
|
||||
print_stats("Client-Server APIs", summary["client"], test_mappings["client_apis"], verbose)
|
||||
print_stats("Federation APIs", summary["federation"], test_mappings["federation_apis"], verbose)
|
||||
if verbose:
|
||||
print("The following tests don't have a group:")
|
||||
for name in groupless_tests:
|
||||
print(" %s" % (name,))
|
||||
else:
|
||||
print("%d tests don't have a group" % len(groupless_tests))
|
||||
print_stats("Application Services APIs", summary["appservice"], test_mappings, verbose)
|
||||
|
||||
|
||||
|
||||
@ -257,4 +263,4 @@ if __name__ == '__main__':
|
||||
parser.add_argument("tap_file", help="path to results.tap")
|
||||
parser.add_argument("-v", action="store_true", help="show individual test names in output")
|
||||
args = parser.parse_args()
|
||||
main(args.tap_file, args.v)
|
||||
main(args.tap_file, args.v)
|
Loading…
Reference in New Issue
Block a user