mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-05-25 19:14:02 +00:00
refactor: Ruma upstreaming, half-baked edition
Co-authored-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
Generated
+91
-114
@@ -1003,6 +1003,7 @@ dependencies = [
|
||||
"rand 0.10.1",
|
||||
"reqwest",
|
||||
"ruma",
|
||||
"ruminuwuity",
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
@@ -1076,7 +1077,7 @@ dependencies = [
|
||||
"tikv-jemallocator",
|
||||
"tokio",
|
||||
"tokio-metrics",
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
"toml 1.1.2+spec-1.1.0",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
@@ -1183,6 +1184,7 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest",
|
||||
"ruma",
|
||||
"ruminuwuity",
|
||||
"rustyline-async",
|
||||
"sd-notify",
|
||||
"serde",
|
||||
@@ -1282,16 +1284,6 @@ dependencies = [
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "continuwuity-admin-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.10.0"
|
||||
@@ -1718,16 +1710,6 @@ dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "draupnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.6.0"
|
||||
@@ -2883,30 +2865,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js_option"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c"
|
||||
checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konst"
|
||||
version = "0.3.16"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9"
|
||||
checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d"
|
||||
dependencies = [
|
||||
"const_panic",
|
||||
"konst_kernel",
|
||||
"typewit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konst_kernel"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c"
|
||||
dependencies = [
|
||||
"typewit",
|
||||
]
|
||||
|
||||
@@ -3237,16 +3209,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "meowlnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -4274,6 +4236,8 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -4283,7 +4247,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
@@ -4298,6 +4262,16 @@ dependencies = [
|
||||
"rand_core 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
@@ -4360,7 +4334,7 @@ dependencies = [
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand 0.9.3",
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"simd_helpers",
|
||||
"thiserror 2.0.18",
|
||||
"v_frame",
|
||||
@@ -4527,31 +4501,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"continuwuity-admin-api",
|
||||
"draupnir-antispam",
|
||||
"js_int",
|
||||
"js_option",
|
||||
"meowlnir-antispam",
|
||||
"ruma-appservice-api",
|
||||
"ruma-client-api",
|
||||
"ruma-common",
|
||||
"ruma-events",
|
||||
"ruma-federation-api",
|
||||
"ruma-identifiers-validation",
|
||||
"ruma-identity-service-api",
|
||||
"ruma-push-gateway-api",
|
||||
"ruma-signatures",
|
||||
"ruma-state-res",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4562,13 +4532,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.22.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
"bytes",
|
||||
"date_header",
|
||||
"http",
|
||||
"js_int",
|
||||
"js_option",
|
||||
@@ -4585,12 +4554,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.17.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"date_header",
|
||||
"form_urlencoded",
|
||||
"getrandom 0.2.17",
|
||||
"http",
|
||||
@@ -4598,14 +4568,13 @@ dependencies = [
|
||||
"js_int",
|
||||
"konst",
|
||||
"percent-encoding",
|
||||
"rand 0.10.1",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"ruma-identifiers-validation",
|
||||
"ruma-macros",
|
||||
"serde",
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tracing",
|
||||
@@ -4613,37 +4582,34 @@ dependencies = [
|
||||
"uuid",
|
||||
"web-time",
|
||||
"wildmatch",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.32.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
"js_int",
|
||||
"js_option",
|
||||
"percent-encoding",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"ruma-common",
|
||||
"ruma-identifiers-validation",
|
||||
"ruma-macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"url",
|
||||
"web-time",
|
||||
"wildmatch",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.13.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
@@ -4653,9 +4619,10 @@ dependencies = [
|
||||
"js_int",
|
||||
"memchr",
|
||||
"mime",
|
||||
"rand 0.10.1",
|
||||
"rand 0.8.5",
|
||||
"ruma-common",
|
||||
"ruma-events",
|
||||
"ruma-signatures",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
@@ -4664,28 +4631,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.17.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -4693,13 +4651,13 @@ dependencies = [
|
||||
"ruma-identifiers-validation",
|
||||
"serde",
|
||||
"syn",
|
||||
"toml 0.8.23",
|
||||
"toml 1.1.2+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4710,21 +4668,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
"memchr",
|
||||
"pkcs8",
|
||||
"rand 0.10.1",
|
||||
"rand_core 0.6.4",
|
||||
"rand 0.8.5",
|
||||
"ruma-common",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subslice",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
"ruma-events",
|
||||
"ruma-signatures",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruminuwuity"
|
||||
version = "0.5.7-alpha.1"
|
||||
dependencies = [
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wildmatch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-librocksdb-sys"
|
||||
version = "0.42.0+10.10.1"
|
||||
@@ -5436,15 +5418,6 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subslice"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a8e4809a3bb02de01f1f7faf1ba01a83af9e8eabcd4d31dd6e413d14d56aae"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -5773,6 +5746,19 @@ dependencies = [
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_spanned 1.1.1",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
@@ -6071,15 +6057,6 @@ name = "typewit"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc19094686c694eb41b3b99dcc2f2975d4b078512fa22ae6c63f7ca318bdcff7"
|
||||
dependencies = [
|
||||
"typewit_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typewit_proc_macros"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
|
||||
|
||||
[[package]]
|
||||
name = "uname"
|
||||
|
||||
+15
-20
@@ -47,9 +47,9 @@ default-features = false
|
||||
features = ["features"]
|
||||
|
||||
[workspace.dependencies.toml]
|
||||
version = "0.9.5"
|
||||
version = "1.0.0"
|
||||
default-features = false
|
||||
features = ["parse"]
|
||||
features = ["parse", "serde"]
|
||||
|
||||
[workspace.dependencies.sanitize-filename]
|
||||
version = "0.6.0"
|
||||
@@ -342,49 +342,40 @@ version = "0.1.2"
|
||||
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "1415caf8a32af4d943580c5ea4e12be1974593c2"
|
||||
# version = "0.14.1"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "e2e5fece57800a2559ab028fd775a7978f3725e9"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
"federation-api",
|
||||
"markdown",
|
||||
"push-gateway-api-c",
|
||||
"unstable-exhaustive-types",
|
||||
"state-res",
|
||||
"rand",
|
||||
"markdown",
|
||||
"ring-compat",
|
||||
"compat-upload-signatures",
|
||||
"identifiers-validation",
|
||||
"unstable-unspecified",
|
||||
"unstable-msc2448",
|
||||
"unstable-msc2666",
|
||||
"unstable-msc2867",
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3814",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3266",
|
||||
"unstable-msc3381", # polls
|
||||
"unstable-msc3489", # beacon / live location
|
||||
"unstable-msc3575",
|
||||
"unstable-msc3930", # polls push rules
|
||||
"unstable-msc4075",
|
||||
"unstable-msc4095",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4210", # remove legacy mentions
|
||||
"unstable-extensible-events",
|
||||
"unstable-pdu",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4310",
|
||||
"unstable-msc4380",
|
||||
"unstable-msc4143", # livekit well_known response
|
||||
"unstable-msc4284",
|
||||
"unstable-msc4439", # pgp_key in .well_known/matrix/support
|
||||
"unstable-extensible-events",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
@@ -657,6 +648,10 @@ default-features = false
|
||||
package = "conduwuit"
|
||||
path = "src/main"
|
||||
|
||||
[workspace.dependencies.ruminuwuity]
|
||||
package = "ruminuwuity"
|
||||
path = "src/ruminuwuity"
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Release profiles
|
||||
|
||||
@@ -93,6 +93,7 @@ log.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest.workspace = true
|
||||
ruma.workspace = true
|
||||
ruminuwuity.workspace = true
|
||||
serde_html_form.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedRoomAliasId, continuwuity_admin_api::rooms,
|
||||
OwnedRoomAliasId,
|
||||
events::room::message::RoomMessageEventContent,
|
||||
};
|
||||
use ruminuwuity::admin::continuwuity::rooms;
|
||||
|
||||
use crate::{Ruma, client::leave_room};
|
||||
|
||||
@@ -36,7 +37,6 @@ pub(crate) async fn ban_room(
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.ready_filter(|user| services.globals.user_is_local(user))
|
||||
.boxed();
|
||||
let mut evicted = Vec::new();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::{OwnedRoomId, continuwuity_admin_api::rooms};
|
||||
use ruma::OwnedRoomId;
|
||||
use ruminuwuity::admin::continuwuity::rooms;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -22,7 +23,7 @@ pub(crate) async fn list_rooms(
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.filter_map(|room_id| async move {
|
||||
if !services.rooms.metadata.is_banned(room_id).await {
|
||||
if !services.rooms.metadata.is_banned(&room_id).await {
|
||||
Some(room_id.to_owned())
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(crate) async fn get_register_available_route(
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
Ok(get_username_availability::v3::Response::new(true))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
@@ -194,7 +194,7 @@ pub(crate) async fn change_password_route(
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(change_password::v3::Response {})
|
||||
Ok(change_password::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/password/email/requestToken`
|
||||
|
||||
@@ -218,14 +218,7 @@ pub(crate) async fn register_route(
|
||||
.await?;
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let no_device = body.inhibit_login
|
||||
|| body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|aps| aps.registration.device_management);
|
||||
|
||||
let (token, device) = if !no_device {
|
||||
// Don't create a device for inhibited logins
|
||||
let (token, device) = if !body.inhibit_login {
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
@@ -243,11 +236,12 @@ pub(crate) async fn register_route(
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
(Some(new_token), Some(device_id))
|
||||
} else {
|
||||
// Don't create a device for inhibited logins
|
||||
(None, None)
|
||||
};
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
// If the user registered with an email, associate it with their account.
|
||||
if let Some(identity) = identity
|
||||
|
||||
@@ -40,7 +40,7 @@ pub(crate) async fn set_global_account_data_route(
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_global_account_data::v3::Response {})
|
||||
Ok(set_global_account_data::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
|
||||
@@ -65,7 +65,7 @@ pub(crate) async fn set_room_account_data_route(
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_room_account_data::v3::Response {})
|
||||
Ok(set_room_account_data::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||
@@ -119,7 +119,7 @@ async fn set_account_data(
|
||||
event_type_s: &str,
|
||||
data: &RawJsonValue,
|
||||
) -> Result {
|
||||
if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
|
||||
if event_type_s == "m.fully_read" {
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint cannot be used for marking a room as fully read (setting \
|
||||
m.fully_read)"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use futures::future::{join, join3};
|
||||
use ruma::api::client::admin::{get_suspended, set_suspended};
|
||||
use ruminuwuity::admin::{get_suspended, set_suspended};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
|
||||
@@ -47,5 +47,5 @@ pub(crate) async fn appservice_ping(
|
||||
.await?
|
||||
.expect("We already validated if an appservice URL exists above");
|
||||
|
||||
Ok(request_ping::v1::Response { duration: timer.elapsed() })
|
||||
Ok(request_ping::v1::Response::new(timer.elapsed()))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn create_backup_version_route(
|
||||
.key_backups
|
||||
.create_backup(body.sender_user(), &body.algorithm)?;
|
||||
|
||||
Ok(create_backup_version::v3::Response { version })
|
||||
Ok(create_backup_version::v3::Response::new(version))
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
|
||||
@@ -44,7 +44,7 @@ pub(crate) async fn update_backup_version_route(
|
||||
.update_backup(body.sender_user(), &body.version, &body.algorithm)
|
||||
.await?;
|
||||
|
||||
Ok(update_backup_version::v3::Response {})
|
||||
Ok(update_backup_version::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/room_keys/version`
|
||||
@@ -105,7 +105,7 @@ pub(crate) async fn delete_backup_version_route(
|
||||
.delete_backup(body.sender_user(), &body.version)
|
||||
.await;
|
||||
|
||||
Ok(delete_backup_version::v3::Response {})
|
||||
Ok(delete_backup_version::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/room_keys/keys`
|
||||
@@ -292,7 +292,7 @@ pub(crate) async fn get_backup_keys_route(
|
||||
.get_all(body.sender_user(), &body.version)
|
||||
.await;
|
||||
|
||||
Ok(get_backup_keys::v3::Response { rooms })
|
||||
Ok(get_backup_keys::v3::Response::new(rooms))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
|
||||
@@ -307,7 +307,7 @@ pub(crate) async fn get_backup_keys_for_room_route(
|
||||
.get_room(body.sender_user(), &body.version, &body.room_id)
|
||||
.await;
|
||||
|
||||
Ok(get_backup_keys_for_room::v3::Response { sessions })
|
||||
Ok(get_backup_keys_for_room::v3::Response::new(sessions))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
|
||||
@@ -325,7 +325,7 @@ pub(crate) async fn get_backup_keys_for_session_route(
|
||||
err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
|
||||
})?;
|
||||
|
||||
Ok(get_backup_keys_for_session::v3::Response { key_data })
|
||||
Ok(get_backup_keys_for_session::v3::Response::new(key_data))
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/r0/room_keys/keys`
|
||||
@@ -342,7 +342,7 @@ pub(crate) async fn delete_backup_keys_route(
|
||||
|
||||
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
|
||||
|
||||
Ok(delete_backup_keys::v3::Response { count, etag })
|
||||
Ok(delete_backup_keys::v3::Response::new(etag, count))
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
|
||||
@@ -359,7 +359,7 @@ pub(crate) async fn delete_backup_keys_for_room_route(
|
||||
|
||||
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
|
||||
|
||||
Ok(delete_backup_keys_for_room::v3::Response { count, etag })
|
||||
Ok(delete_backup_keys_for_room::v3::Response::new(etag, count))
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
|
||||
@@ -376,7 +376,7 @@ pub(crate) async fn delete_backup_keys_for_session_route(
|
||||
|
||||
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
|
||||
|
||||
Ok(delete_backup_keys_for_session::v3::Response { count, etag })
|
||||
Ok(delete_backup_keys_for_session::v3::Response::new(etag, count))
|
||||
}
|
||||
|
||||
async fn get_count_etag(
|
||||
|
||||
@@ -56,5 +56,5 @@ pub(crate) async fn get_capabilities_route(
|
||||
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
|
||||
}
|
||||
|
||||
Ok(get_capabilities::v3::Response { capabilities })
|
||||
Ok(get_capabilities::v3::Response::new(capabilities))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ pub(crate) async fn put_dehydrated_device_route(
|
||||
.set_dehydrated_device(sender_user, body.body)
|
||||
.await?;
|
||||
|
||||
Ok(put_dehydrated_device::Response { device_id })
|
||||
Ok(put_dehydrated_device::Response::new(device_id))
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/../dehydrated_device`
|
||||
@@ -51,7 +51,7 @@ pub(crate) async fn delete_dehydrated_device_route(
|
||||
|
||||
services.users.remove_device(sender_user, &device_id).await;
|
||||
|
||||
Ok(delete_dehydrated_device::Response { device_id })
|
||||
Ok(delete_dehydrated_device::Response::new(device_id))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/../dehydrated_device`
|
||||
@@ -67,10 +67,7 @@ pub(crate) async fn get_dehydrated_device_route(
|
||||
|
||||
let device = services.users.get_dehydrated_device(sender_user).await?;
|
||||
|
||||
Ok(get_dehydrated_device::Response {
|
||||
device_id: device.device_id,
|
||||
device_data: device.device_data,
|
||||
})
|
||||
Ok(get_dehydrated_device::Response::new(device.device_id, device.device_data))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
|
||||
|
||||
@@ -25,7 +25,7 @@ pub(crate) async fn get_devices_route(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(get_devices::v3::Response { devices })
|
||||
Ok(get_devices::v3::Response::new(devices))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/devices/{deviceId}`
|
||||
@@ -41,7 +41,7 @@ pub(crate) async fn get_device_route(
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
|
||||
|
||||
Ok(get_device::v3::Response { device })
|
||||
Ok(get_device::v3::Response::new(device))
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
|
||||
@@ -73,19 +73,15 @@ pub(crate) async fn update_device_route(
|
||||
.update_device_metadata(sender_user, &body.device_id, &device)
|
||||
.await?;
|
||||
|
||||
Ok(update_device::v3::Response {})
|
||||
Ok(update_device::v3::Response::new())
|
||||
},
|
||||
| Err(_) => {
|
||||
let Some(appservice) = appservice else {
|
||||
return Err!(Request(NotFound("Device not found.")));
|
||||
};
|
||||
if !appservice.registration.device_management {
|
||||
return Err!(Request(NotFound("Device not found.")));
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Creating new device for {sender_user} from appservice {} as MSC4190 is enabled \
|
||||
and device ID does not exist",
|
||||
"Creating new device for {sender_user} from appservice {} as device ID does not exist",
|
||||
appservice.registration.id
|
||||
);
|
||||
|
||||
@@ -102,7 +98,7 @@ pub(crate) async fn update_device_route(
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(update_device::v3::Response {});
|
||||
return Ok(update_device::v3::Response::new());
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -124,17 +120,16 @@ pub(crate) async fn delete_device_route(
|
||||
let sender_user = body.sender_user();
|
||||
let appservice = body.appservice_info.as_ref();
|
||||
|
||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||
if appservice.is_some() {
|
||||
debug!(
|
||||
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
||||
enabled"
|
||||
"Skipping UIAA for {sender_user} as this is from an appservice"
|
||||
);
|
||||
services
|
||||
.users
|
||||
.remove_device(sender_user, &body.device_id)
|
||||
.await;
|
||||
|
||||
return Ok(delete_device::v3::Response {});
|
||||
return Ok(delete_device::v3::Response::new());
|
||||
}
|
||||
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
future::{join, join4, join5},
|
||||
};
|
||||
use ruma::{
|
||||
OwnedRoomId, RoomId, ServerName, UInt, UserId,
|
||||
api::{
|
||||
OwnedRoomId, RoomId, ServerName, UInt, UserId, api::{
|
||||
client::{
|
||||
directory::{
|
||||
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
||||
@@ -27,17 +26,14 @@
|
||||
room,
|
||||
},
|
||||
federation,
|
||||
},
|
||||
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
|
||||
events::{
|
||||
}, directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter}, events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
create::RoomCreateEventContent,
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
},
|
||||
uint,
|
||||
}, room::JoinRuleKind, uint
|
||||
};
|
||||
use tokio::join;
|
||||
|
||||
@@ -425,7 +421,7 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
.state_accessor
|
||||
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
|
||||
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
|
||||
| JoinRule::Public => PublicRoomJoinRule::Public,
|
||||
| JoinRule::Public => JoinRuleKind::Public,
|
||||
| JoinRule::Knock => "knock".into(),
|
||||
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
|
||||
| _ => "invite".into(),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
};
|
||||
use reqwest::Url;
|
||||
use ruma::{
|
||||
Mxc, UserId,
|
||||
UserId,
|
||||
api::client::{
|
||||
authenticated_media::{
|
||||
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
|
||||
|
||||
@@ -24,30 +24,29 @@ pub(crate) async fn ban_user_route(
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
|
||||
let current_member_content = services
|
||||
let mut content = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
.await
|
||||
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
|
||||
|
||||
content.membership = MembershipState::Ban;
|
||||
content.reason = body.reason.clone();
|
||||
content.displayname = None;
|
||||
content.avatar_url = None;
|
||||
content.is_direct = None;
|
||||
content.join_authorized_via_users_server = None;
|
||||
content.third_party_invite = None;
|
||||
// TODO(upstream): MSC4293
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Ban,
|
||||
reason: body.reason.clone(),
|
||||
displayname: None, // display name may be offensive
|
||||
avatar_url: None, // avatar may be offensive
|
||||
is_direct: None,
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
redact_events: body.redact_events,
|
||||
..current_member_content
|
||||
}),
|
||||
PduBuilder::state(body.user_id.to_string(), &content),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
RoomId, UserId,
|
||||
api::{client::membership::invite_user, federation::membership::create_invite},
|
||||
events::{
|
||||
invite_permission_config::FilterLevel,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
api::{client::membership::invite_user, federation::membership::{RawStrippedState, create_invite}},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
};
|
||||
use ruminuwuity::invite_permission_config::FilterLevel;
|
||||
use service::Services;
|
||||
|
||||
use super::banned_room_check;
|
||||
@@ -59,7 +57,7 @@ pub(crate) async fn invite_user_route(
|
||||
|
||||
if !matches!(sender_filter_level, FilterLevel::Allow) {
|
||||
// drop invites if the sender has the recipient filtered
|
||||
return Ok(invite_user::v3::Response {});
|
||||
return Ok(invite_user::v3::Response::new());
|
||||
}
|
||||
|
||||
if let Ok(target_user_membership) = services
|
||||
@@ -101,7 +99,7 @@ pub(crate) async fn invite_user_route(
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(invite_user::v3::Response {})
|
||||
Ok(invite_user::v3::Response::new())
|
||||
},
|
||||
| _ => {
|
||||
Err!(Request(NotFound("User not found.")))
|
||||
@@ -141,12 +139,11 @@ pub(crate) async fn invite_helper(
|
||||
let (pdu, pdu_json, invite_room_state) = {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
};
|
||||
let mut content = RoomMemberEventContent::new(MembershipState::Invite);
|
||||
content.displayname = services.users.displayname(recipient_user).await.ok();
|
||||
content.avatar_url = services.users.avatar_url(recipient_user).await.ok();
|
||||
content.is_direct = Some(is_direct);
|
||||
content.reason = reason;
|
||||
|
||||
let (pdu, pdu_json) = services
|
||||
.rooms
|
||||
@@ -159,7 +156,15 @@ pub(crate) async fn invite_helper(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await;
|
||||
#[allow(deprecated)]
|
||||
let invite_room_state = services
|
||||
.rooms
|
||||
.state
|
||||
.summary_stripped(&pdu, room_id)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|event| RawStrippedState::Stripped(event))
|
||||
.collect();
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
@@ -168,24 +173,26 @@ pub(crate) async fn invite_helper(
|
||||
|
||||
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
|
||||
|
||||
let mut request = create_invite::v2::Request::new(
|
||||
room_id.to_owned(),
|
||||
(*pdu.event_id).to_owned(),
|
||||
room_version_id.clone(),
|
||||
services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu_json.clone())
|
||||
.await,
|
||||
invite_room_state,
|
||||
);
|
||||
request.via = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_route_via(room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(recipient_user.server_name(), create_invite::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: (*pdu.event_id).to_owned(),
|
||||
room_version: room_version_id.clone(),
|
||||
event: services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu_json.clone())
|
||||
.await,
|
||||
invite_room_state,
|
||||
via: services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_route_via(room_id)
|
||||
.await
|
||||
.ok(),
|
||||
})
|
||||
.send_federation_request(recipient_user.server_name(), request)
|
||||
.await?;
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and
|
||||
@@ -229,14 +236,12 @@ pub(crate) async fn invite_helper(
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(recipient_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
|
||||
blurhash: services.users.blurhash(recipient_user).await.ok(),
|
||||
is_direct: Some(is_direct),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Invite)
|
||||
};
|
||||
let mut content = RoomMemberEventContent::new(MembershipState::Invite);
|
||||
content.displayname = services.users.displayname(recipient_user).await.ok();
|
||||
content.avatar_url = services.users.avatar_url(recipient_user).await.ok();
|
||||
content.blurhash = services.users.blurhash(recipient_user).await.ok();
|
||||
content.is_direct = Some(is_direct);
|
||||
content.reason = reason;
|
||||
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -89,7 +89,6 @@ pub(crate) async fn join_room_by_id_route(
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
@@ -168,7 +167,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
);
|
||||
@@ -212,8 +210,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
let addl_via_servers = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned);
|
||||
.servers_invite_via(&room_id);
|
||||
|
||||
let addl_state_servers = services
|
||||
.rooms
|
||||
@@ -226,7 +223,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
.iter()
|
||||
.map(|event| event.get_field("sender"))
|
||||
.filter_map(FlatOk::flat_ok)
|
||||
.map(|user: &UserId| user.server_name().to_owned())
|
||||
.map(|user: OwnedUserId| user.server_name().to_owned())
|
||||
.stream()
|
||||
.chain(addl_via_servers)
|
||||
.collect()
|
||||
@@ -252,7 +249,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id })
|
||||
Ok(join_room_by_id_or_alias::v3::Response::new(join_room_response.room_id))
|
||||
}
|
||||
|
||||
pub async fn join_room_by_id_helper(
|
||||
@@ -283,7 +280,7 @@ pub async fn join_room_by_id_helper(
|
||||
.await
|
||||
{
|
||||
debug_warn!("{sender_user} is already joined in {room_id}");
|
||||
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
|
||||
return Ok(join_room_by_id::v3::Response::new(room_id.to_owned()));
|
||||
}
|
||||
|
||||
if let Err(e) = services
|
||||
@@ -423,16 +420,17 @@ async fn join_room_by_id_helper_remote(
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
|
||||
let mut join_content = RoomMemberEventContent::new(MembershipState::Join);
|
||||
join_content.displayname = services.users.displayname(sender_user).await.ok();
|
||||
join_content.avatar_url = services.users.avatar_url(sender_user).await.ok();
|
||||
join_content.blurhash = services.users.blurhash(sender_user).await.ok();
|
||||
join_content.reason = reason;
|
||||
join_content.join_authorized_via_users_server = join_authorized_via_users_server.clone();
|
||||
|
||||
join_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason,
|
||||
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
|
||||
..RoomMemberEventContent::new(MembershipState::Join)
|
||||
})
|
||||
to_canonical_value(join_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
@@ -462,15 +460,10 @@ async fn join_room_by_id_helper_remote(
|
||||
let mut join_event = join_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_join in room {room_id}");
|
||||
let send_join_request = federation::membership::create_join_event::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
omit_members: false,
|
||||
pdu: services
|
||||
let send_join_request = federation::membership::create_join_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(join_event.clone())
|
||||
.await,
|
||||
};
|
||||
.await);
|
||||
|
||||
let send_join_response = match services
|
||||
.sending
|
||||
@@ -638,7 +631,7 @@ async fn join_room_by_id_helper_remote(
|
||||
};
|
||||
|
||||
let auth_check = state_res::event_auth::auth_check(
|
||||
&state_res::RoomVersion::new(&room_version_id)?,
|
||||
&room_version_id.rules().unwrap(),
|
||||
&parsed_join_pdu,
|
||||
None, // TODO: third party invite
|
||||
|k, s| state_fetch(k.clone(), s.into()),
|
||||
@@ -759,14 +752,12 @@ async fn join_room_by_id_helper_local(
|
||||
}
|
||||
}
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason: reason.clone(),
|
||||
join_authorized_via_users_server: auth_user,
|
||||
..RoomMemberEventContent::new(MembershipState::Join)
|
||||
};
|
||||
let mut content = RoomMemberEventContent::new(MembershipState::Join);
|
||||
content.displayname = services.users.displayname(sender_user).await.ok();
|
||||
content.avatar_url = services.users.avatar_url(sender_user).await.ok();
|
||||
content.blurhash = services.users.blurhash(sender_user).await.ok();
|
||||
content.reason = reason.clone();
|
||||
content.join_authorized_via_users_server = auth_user;
|
||||
|
||||
// Try normal join first
|
||||
let Err(error) = services
|
||||
@@ -822,15 +813,15 @@ async fn make_join_request(
|
||||
"Asking {remote_server} for make_join (attempt {make_join_counter}/{})",
|
||||
servers.len()
|
||||
);
|
||||
|
||||
let mut request = federation::membership::prepare_join_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned());
|
||||
request.ver = services.server.supported_room_versions().collect();
|
||||
|
||||
let make_join_response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
remote_server,
|
||||
federation::membership::prepare_join_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: sender_user.to_owned(),
|
||||
ver: services.server.supported_room_versions().collect(),
|
||||
},
|
||||
request
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ pub(crate) async fn kick_user_route(
|
||||
if services.users.is_suspended(sender_user).await? {
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
|
||||
let Ok(event) = services
|
||||
let Ok(mut event) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
@@ -41,18 +41,17 @@ pub(crate) async fn kick_user_route(
|
||||
)));
|
||||
}
|
||||
|
||||
event.membership = MembershipState::Leave;
|
||||
event.reason = body.reason.clone();
|
||||
event.is_direct = None;
|
||||
event.join_authorized_via_users_server = None;
|
||||
event.third_party_invite = None;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason: body.reason.clone(),
|
||||
is_direct: None,
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
..event
|
||||
}),
|
||||
PduBuilder::state(body.user_id.to_string(), &event),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
|
||||
@@ -15,20 +15,16 @@
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
api::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, RoomVersionId, UserId, api::{
|
||||
client::knock::knock_room,
|
||||
federation::{self},
|
||||
},
|
||||
canonical_json::to_canonical_value,
|
||||
events::{
|
||||
}, canonical_json::to_canonical_value, events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
join_rules::{AllowRule, JoinRule},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
use service::{
|
||||
Services,
|
||||
@@ -73,7 +69,6 @@ pub(crate) async fn knock_room_route(
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
);
|
||||
@@ -116,8 +111,7 @@ pub(crate) async fn knock_room_route(
|
||||
let addl_via_servers = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned);
|
||||
.servers_invite_via(&room_id);
|
||||
|
||||
let addl_state_servers = services
|
||||
.rooms
|
||||
@@ -130,7 +124,7 @@ pub(crate) async fn knock_room_route(
|
||||
.iter()
|
||||
.map(|event| event.get_field("sender"))
|
||||
.filter_map(FlatOk::flat_ok)
|
||||
.map(|user: &UserId| user.server_name().to_owned())
|
||||
.map(|user: OwnedUserId| user.server_name().to_owned())
|
||||
.stream()
|
||||
.chain(addl_via_servers)
|
||||
.collect()
|
||||
@@ -188,7 +182,7 @@ async fn knock_room_by_id_helper(
|
||||
.await
|
||||
{
|
||||
debug_warn!("{sender_user} is already knocked in {room_id}");
|
||||
return Ok(knock_room::v3::Response { room_id: room_id.into() });
|
||||
return Ok(knock_room::v3::Response::new(room_id.into()));
|
||||
}
|
||||
|
||||
if let Ok(membership) = services
|
||||
@@ -353,13 +347,11 @@ async fn knock_room_helper_local(
|
||||
return Err!(Request(Forbidden("This room does not support knocking.")));
|
||||
}
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason: reason.clone(),
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
};
|
||||
let mut content = RoomMemberEventContent::new(MembershipState::Knock);
|
||||
content.displayname = services.users.displayname(sender_user).await.ok();
|
||||
content.avatar_url = services.users.avatar_url(sender_user).await.ok();
|
||||
content.blurhash = services.users.blurhash(sender_user).await.ok();
|
||||
content.reason = reason.clone();
|
||||
|
||||
// Try normal knock first
|
||||
let Err(error) = services
|
||||
@@ -424,13 +416,7 @@ async fn knock_room_helper_local(
|
||||
);
|
||||
knock_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
})
|
||||
to_canonical_value(content)
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
@@ -451,14 +437,14 @@ async fn knock_room_helper_local(
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::knock::send_knock::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
let send_knock_request = federation::membership::create_knock_event::v1::Request::new(
|
||||
room_id.to_owned(),
|
||||
event_id.clone(),
|
||||
services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(knock_event.clone())
|
||||
.await,
|
||||
};
|
||||
);
|
||||
|
||||
services
|
||||
.sending
|
||||
@@ -545,15 +531,16 @@ async fn knock_room_helper_remote(
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
|
||||
let mut knock_content = RoomMemberEventContent::new(MembershipState::Knock);
|
||||
knock_content.displayname = services.users.displayname(sender_user).await.ok();
|
||||
knock_content.avatar_url = services.users.avatar_url(sender_user).await.ok();
|
||||
knock_content.blurhash = services.users.blurhash(sender_user).await.ok();
|
||||
knock_content.reason = reason;
|
||||
|
||||
knock_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
})
|
||||
to_canonical_value(knock_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
@@ -574,18 +561,18 @@ async fn knock_room_helper_remote(
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::knock::send_knock::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
let request = federation::membership::create_knock_event::v1::Request::new(
|
||||
room_id.to_owned(),
|
||||
event_id.clone(),
|
||||
services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(knock_event.clone())
|
||||
.await,
|
||||
};
|
||||
.await
|
||||
);
|
||||
|
||||
let send_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(&remote_server, send_knock_request)
|
||||
.send_federation_request(&remote_server, request)
|
||||
.await?;
|
||||
|
||||
info!("send_knock finished");
|
||||
@@ -604,7 +591,20 @@ async fn knock_room_helper_remote(
|
||||
let state = send_knock_response
|
||||
.knock_room_state
|
||||
.iter()
|
||||
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
|
||||
.map(|event| {
|
||||
#[allow(deprecated)]
|
||||
let raw_value = match event {
|
||||
federation::membership::RawStrippedState::Stripped(raw_state) => {
|
||||
&raw_state.clone().into_json()
|
||||
},
|
||||
federation::membership::RawStrippedState::Pdu(raw_value) => {
|
||||
raw_value
|
||||
},
|
||||
_ => panic!("unknown raw stripped state type"),
|
||||
};
|
||||
|
||||
serde_json::from_str::<CanonicalJsonObject>(raw_value.get())
|
||||
})
|
||||
.filter_map(Result::ok);
|
||||
|
||||
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
|
||||
@@ -709,7 +709,7 @@ async fn make_knock_request(
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
|
||||
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
|
||||
let mut make_knock_response_and_server =
|
||||
Err!(BadServerResponse("No server available to assist in knocking."));
|
||||
|
||||
@@ -722,15 +722,14 @@ async fn make_knock_request(
|
||||
|
||||
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
|
||||
|
||||
let mut request = federation::membership::prepare_knock_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned());
|
||||
request.ver = services.server.supported_room_versions().collect();
|
||||
|
||||
let make_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
remote_server,
|
||||
federation::knock::create_knock_event_template::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: sender_user.to_owned(),
|
||||
ver: services.server.supported_room_versions().collect(),
|
||||
},
|
||||
request,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -45,8 +45,7 @@ pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
|
||||
let rooms_joined = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(user_id)
|
||||
.map(ToOwned::to_owned);
|
||||
.rooms_joined(user_id);
|
||||
|
||||
let rooms_invited = services
|
||||
.rooms
|
||||
@@ -142,18 +141,17 @@ pub async fn leave_room(
|
||||
.await;
|
||||
|
||||
match user_member_event_content {
|
||||
| Ok(content) => {
|
||||
| Ok(mut content) => {
|
||||
content.membership = MembershipState::Leave;
|
||||
content.reason = reason;
|
||||
content.join_authorized_via_users_server = None;
|
||||
content.is_direct = None;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason,
|
||||
join_authorized_via_users_server: None,
|
||||
is_direct: None,
|
||||
..content
|
||||
}),
|
||||
PduBuilder::state(user_id.to_string(), &content),
|
||||
user_id,
|
||||
Some(room_id),
|
||||
&state_lock,
|
||||
@@ -226,7 +224,6 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
||||
.rooms
|
||||
.state_cache
|
||||
.servers_invite_via(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<HashSet<OwnedServerName>>()
|
||||
.await,
|
||||
);
|
||||
@@ -260,7 +257,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.filter_map(|sender| {
|
||||
if !services.globals.user_is_local(sender) {
|
||||
if !services.globals.user_is_local(&sender) {
|
||||
Some(sender.server_name().to_owned())
|
||||
} else {
|
||||
None
|
||||
@@ -289,10 +286,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
||||
.sending
|
||||
.send_federation_request(
|
||||
remote_server.as_ref(),
|
||||
federation::membership::prepare_leave_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: user_id.to_owned(),
|
||||
},
|
||||
federation::membership::prepare_leave_event::v1::Request::new(room_id.to_owned(), user_id.to_owned())
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -393,14 +387,10 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&remote_server,
|
||||
federation::membership::create_leave_event::v2::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
federation::membership::create_leave_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services
|
||||
.sending
|
||||
.convert_to_outgoing_federation_event(leave_event.clone())
|
||||
.await,
|
||||
},
|
||||
.await),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use futures::{FutureExt, StreamExt, future::join};
|
||||
use ruma::{
|
||||
api::client::membership::{
|
||||
get_member_events::{self, v3::MembershipEventFilter},
|
||||
get_member_events::{self},
|
||||
joined_members::{self, v3::RoomMember},
|
||||
},
|
||||
events::{
|
||||
@@ -43,8 +43,7 @@ pub(crate) async fn get_member_events_route(
|
||||
return Err!(Request(Forbidden("You don't have permission to view this room.")));
|
||||
}
|
||||
|
||||
Ok(get_member_events::v3::Response {
|
||||
chunk: services
|
||||
let chunk = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full(&body.room_id)
|
||||
@@ -55,8 +54,9 @@ pub(crate) async fn get_member_events_route(
|
||||
.map(Event::into_format)
|
||||
.collect()
|
||||
.boxed()
|
||||
.await,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(get_member_events::v3::Response::new(chunk))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members`
|
||||
@@ -78,70 +78,44 @@ pub(crate) async fn joined_members_route(
|
||||
return Err!(Request(Forbidden("You don't have permission to view this room.")));
|
||||
}
|
||||
|
||||
Ok(joined_members::v3::Response {
|
||||
joined: services
|
||||
let joined = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_then(|user_id| async move {
|
||||
let mut member = RoomMember::new();
|
||||
let (display_name, avatar_url) = join(
|
||||
services.users.displayname(&user_id).ok(),
|
||||
services.users.avatar_url(&user_id).ok(),
|
||||
)
|
||||
.await;
|
||||
member.display_name = display_name;
|
||||
member.avatar_url = avatar_url;
|
||||
|
||||
(user_id, RoomMember { display_name, avatar_url })
|
||||
(user_id, member)
|
||||
})
|
||||
.collect()
|
||||
.await,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(joined_members::v3::Response::new(joined))
|
||||
}
|
||||
|
||||
fn membership_filter<Pdu: Event>(
|
||||
pdu: Pdu,
|
||||
for_membership: Option<&MembershipEventFilter>,
|
||||
not_membership: Option<&MembershipEventFilter>,
|
||||
membership_state_filter: Option<&MembershipState>,
|
||||
not_membership_state_filter: Option<&MembershipState>,
|
||||
) -> Option<impl Event> {
|
||||
let membership_state_filter = match for_membership {
|
||||
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
|
||||
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
|
||||
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
|
||||
| Some(MembershipEventFilter::Leave) => MembershipState::Leave,
|
||||
| Some(_) | None => MembershipState::Join,
|
||||
};
|
||||
|
||||
let not_membership_state_filter = match not_membership {
|
||||
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
|
||||
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
|
||||
| Some(MembershipEventFilter::Join) => MembershipState::Join,
|
||||
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
|
||||
| Some(_) | None => MembershipState::Leave,
|
||||
};
|
||||
|
||||
let evt_membership = pdu.get_content::<RoomMemberEventContent>().ok()?.membership;
|
||||
|
||||
if for_membership.is_some() && not_membership.is_some() {
|
||||
if membership_state_filter != evt_membership
|
||||
|| not_membership_state_filter == evt_membership
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(pdu)
|
||||
}
|
||||
} else if for_membership.is_some() && not_membership.is_none() {
|
||||
if membership_state_filter != evt_membership {
|
||||
None
|
||||
} else {
|
||||
Some(pdu)
|
||||
}
|
||||
} else if not_membership.is_some() && for_membership.is_none() {
|
||||
if not_membership_state_filter == evt_membership {
|
||||
None
|
||||
} else {
|
||||
Some(pdu)
|
||||
}
|
||||
} else {
|
||||
Some(pdu)
|
||||
if let Some(membership_state_filter) = membership_state_filter
|
||||
&& *membership_state_filter != evt_membership {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(not_membership_state_filter) = not_membership_state_filter
|
||||
&& *not_membership_state_filter == evt_membership {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(pdu);
|
||||
}
|
||||
|
||||
@@ -47,15 +47,14 @@ pub(crate) async fn joined_rooms_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<joined_rooms::v3::Request>,
|
||||
) -> Result<joined_rooms::v3::Response> {
|
||||
Ok(joined_rooms::v3::Response {
|
||||
joined_rooms: services
|
||||
let joined_rooms = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(body.sender_user())
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(joined_rooms::v3::Response::new(joined_rooms))
|
||||
}
|
||||
|
||||
/// Checks if the room is banned in any way possible and the sender user is not
|
||||
|
||||
@@ -18,9 +18,9 @@ pub(crate) async fn unban_user_route(
|
||||
if services.users.is_suspended(sender_user).await? {
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
|
||||
let current_member_content = services
|
||||
let mut current_member_content = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(&body.room_id, &body.user_id)
|
||||
@@ -34,18 +34,17 @@ pub(crate) async fn unban_user_route(
|
||||
)));
|
||||
}
|
||||
|
||||
current_member_content.membership = MembershipState::Leave;
|
||||
current_member_content.reason = body.reason.clone();
|
||||
current_member_content.join_authorized_via_users_server = None;
|
||||
current_member_content.third_party_invite = None;
|
||||
current_member_content.is_direct = None;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
reason: body.reason.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
is_direct: None,
|
||||
..current_member_content
|
||||
}),
|
||||
PduBuilder::state(body.user_id.to_string(), ¤t_member_content),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
api::client::{
|
||||
report_user,
|
||||
reporting::report_user,
|
||||
room::{report_content, report_room},
|
||||
},
|
||||
events::{Mentions, room::message::RoomMessageEventContent},
|
||||
|
||||
@@ -26,13 +26,12 @@ pub(crate) async fn get_room_aliases_route(
|
||||
return Err!(Request(Forbidden("You don't have permission to view this room.",)));
|
||||
}
|
||||
|
||||
Ok(aliases::v3::Response {
|
||||
aliases: services
|
||||
let aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(aliases::v3::Response::new(aliases))
|
||||
}
|
||||
|
||||
@@ -2,18 +2,15 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info,
|
||||
Err, Result, debug, debug_info, debug_warn, err, info,
|
||||
matrix::{StateKey, pdu::PduBuilder},
|
||||
trace, warn,
|
||||
};
|
||||
use conduwuit_service::{Services, appservice::RegistrationInfo};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
|
||||
api::client::room::{self, create_room},
|
||||
events::{
|
||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, api::client::room::{self, create_room}, events::{
|
||||
TimelineEventType,
|
||||
invite_permission_config::FilterLevel,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
@@ -25,10 +22,9 @@
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
},
|
||||
int,
|
||||
serde::{JsonObject, Raw},
|
||||
}, int, room_version_rules::RoomIdFormatVersion, serde::{JsonObject, Raw}
|
||||
};
|
||||
use ruminuwuity::invite_permission_config::FilterLevel;
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
|
||||
use crate::{Ruma, client::invite_helper};
|
||||
@@ -81,15 +77,11 @@ pub(crate) async fn create_room_route(
|
||||
},
|
||||
| None => services.server.config.default_room_version.clone(),
|
||||
};
|
||||
let room_features = RoomVersion::new(&room_version)?;
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
let room_id: Option<OwnedRoomId> = if !room_features.room_ids_as_hashes {
|
||||
match &body.room_id {
|
||||
| Some(custom_room_id) => Some(custom_room_id_check(&services, custom_room_id)?),
|
||||
| None => Some(RoomId::new(services.globals.server_name())),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format {
|
||||
RoomIdFormatVersion::V1 => Some(RoomId::new_v1(services.globals.server_name())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
@@ -167,7 +159,7 @@ pub(crate) async fn create_room_route(
|
||||
use RoomVersionId::*;
|
||||
|
||||
let mut content = content
|
||||
.deserialize_as::<CanonicalJsonObject>()
|
||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(error!(
|
||||
"Failed to deserialise content as canonical JSON: {e}"
|
||||
@@ -201,8 +193,7 @@ pub(crate) async fn create_room_route(
|
||||
let content = match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(sender_user.to_owned()),
|
||||
| V11 => RoomCreateEventContent::new_v11(),
|
||||
| _ => RoomCreateEventContent::new_v12(),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
};
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
@@ -257,30 +248,23 @@ pub(crate) async fn create_room_route(
|
||||
},
|
||||
};
|
||||
drop(state_lock);
|
||||
if let Some(expected_room_id) = body.room_id.as_ref() {
|
||||
if expected_room_id.as_str() != room_id.as_str() {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Custom room ID {expected_room_id} does not match the generated room ID \
|
||||
{room_id}.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
debug!("Room created with ID {room_id}");
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
|
||||
|
||||
// 2. Let the room creator join
|
||||
|
||||
let mut join_event = RoomMemberEventContent::new(MembershipState::Join);
|
||||
join_event.displayname = services.users.displayname(sender_user).await.ok();
|
||||
join_event.avatar_url = services.users.avatar_url(sender_user).await.ok();
|
||||
join_event.blurhash = services.users.blurhash(sender_user).await.ok();
|
||||
join_event.is_direct = Some(body.is_direct);
|
||||
|
||||
debug_info!("Joining {sender_user} to room {room_id}");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
is_direct: Some(body.is_direct),
|
||||
..RoomMemberEventContent::new(MembershipState::Join)
|
||||
}),
|
||||
PduBuilder::state(sender_user.to_string(), &join_event),
|
||||
sender_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
@@ -306,7 +290,7 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
|
||||
// Do we care about additional_creators?
|
||||
if room_features.explicitly_privilege_room_creators {
|
||||
if room_version_rules.explicitly_privilege_room_creators {
|
||||
// Have they been specified?
|
||||
if let Some(additional_creators) = create_content.get("additional_creators") {
|
||||
// Are they a real array?
|
||||
@@ -667,60 +651,3 @@ async fn room_alias_check(
|
||||
|
||||
Ok(full_room_alias)
|
||||
}
|
||||
|
||||
/// if a room is being created with a custom room ID, run our checks against it
|
||||
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
// apply forbidden room alias checks to custom room IDs too
|
||||
if services
|
||||
.globals
|
||||
.forbidden_alias_names()
|
||||
.is_match(custom_room_id)
|
||||
{
|
||||
return Err!(Request(Unknown("Custom room ID is forbidden.")));
|
||||
}
|
||||
|
||||
if custom_room_id.contains(':') {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Custom room ID contained `:` which is not allowed. Please note that this expects a \
|
||||
localpart, not the full room ID.",
|
||||
)));
|
||||
} else if custom_room_id.contains(char::is_whitespace) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Custom room ID contained spaces which is not valid."
|
||||
)));
|
||||
}
|
||||
|
||||
let server_name = services.globals.server_name();
|
||||
let mut room_id = custom_room_id.to_owned();
|
||||
if custom_room_id.contains(':') {
|
||||
if !custom_room_id.starts_with('!') {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Custom room ID contains an unexpected `:` which is not allowed.",
|
||||
)));
|
||||
}
|
||||
} else if custom_room_id.starts_with('!') {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Room ID is prefixed with !, but is not fully qualified. You likely did not want \
|
||||
this.",
|
||||
)));
|
||||
} else {
|
||||
room_id = format!("!{custom_room_id}:{server_name}");
|
||||
}
|
||||
OwnedRoomId::parse(room_id)
|
||||
.map_err(Into::into)
|
||||
.and_then(|full_room_id| {
|
||||
if full_room_id
|
||||
.server_name()
|
||||
.expect("failed to extract server name from room ID")
|
||||
!= server_name
|
||||
{
|
||||
Err!(Request(InvalidParam("Custom room ID must be on this server.",)))
|
||||
} else {
|
||||
Ok(full_room_id)
|
||||
}
|
||||
})
|
||||
.inspect(|full_room_id| {
|
||||
debug_info!(%full_room_id, "Full custom room ID");
|
||||
})
|
||||
.inspect_err(|e| warn!(?e, %custom_room_id, "Failed to create room with custom room ID",))
|
||||
}
|
||||
|
||||
@@ -44,15 +44,16 @@ impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
|
||||
$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*
|
||||
{
|
||||
fn add_routes(&'static self, router: Router<State>) -> Router<State> {
|
||||
Req::METADATA
|
||||
.history
|
||||
use ruma::api::path_builder::PathBuilder;
|
||||
|
||||
Req::PATH_BUILDER
|
||||
.all_paths()
|
||||
.fold(router, |router, path| self.add_route(router, path))
|
||||
}
|
||||
|
||||
fn add_route(&'static self, router: Router<State>, path: &str) -> Router<State> {
|
||||
let action = |$($tx,)* req| self($($tx,)* req).map_ok(RumaResponse);
|
||||
let method = method_to_filter(&Req::METADATA.method);
|
||||
let method = method_to_filter(&Req::METHOD);
|
||||
router.route(path, on(method, action))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,6 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export
|
||||
#[error(transparent)]
|
||||
JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export
|
||||
#[error(transparent)]
|
||||
Path(#[from] axum::extract::rejection::PathRejection),
|
||||
#[error("Mutex poisoned: {0}")]
|
||||
Poison(Cow<'static, str>),
|
||||
@@ -90,7 +86,7 @@ pub enum Error {
|
||||
// ruma/conduwuit
|
||||
#[error("Arithmetic operation failed: {0}")]
|
||||
Arithmetic(Cow<'static, str>),
|
||||
#[error("{0}: {1}")]
|
||||
#[error("{0:?}: {1}")]
|
||||
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove
|
||||
#[error("{0}")]
|
||||
BadServerResponse(Cow<'static, str>),
|
||||
@@ -120,7 +116,7 @@ pub enum Error {
|
||||
Mxid(#[from] ruma::IdParseError),
|
||||
#[error("from {0}: {1}")]
|
||||
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
|
||||
#[error("{0}: {1}")]
|
||||
#[error("{0:?}: {1}")]
|
||||
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
|
||||
#[error(transparent)]
|
||||
Ruma(#[from] ruma::api::client::error::Error),
|
||||
@@ -167,13 +163,13 @@ pub fn message(&self) -> String {
|
||||
/// Returns the Matrix error code / error kind
|
||||
#[inline]
|
||||
pub fn kind(&self) -> ruma::api::client::error::ErrorKind {
|
||||
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
|
||||
use ruma::api::client::error::ErrorKind::{Unknown, Unrecognized};
|
||||
|
||||
match self {
|
||||
| Self::Federation(_, error) | Self::Ruma(error) =>
|
||||
response::ruma_error_kind(error).clone(),
|
||||
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
|
||||
| Self::FeatureDisabled(..) => FeatureDisabled,
|
||||
| Self::FeatureDisabled(..) => Unrecognized,
|
||||
| _ => Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use ruma::api::{
|
||||
OutgoingResponse,
|
||||
client::{
|
||||
error::{ErrorBody, ErrorKind},
|
||||
error::{ErrorBody, ErrorKind, StandardErrorBody},
|
||||
uiaa::UiaaResponse,
|
||||
},
|
||||
};
|
||||
@@ -51,15 +51,9 @@ fn from(error: Error) -> Self {
|
||||
return Self::AuthResponse(uiaainfo);
|
||||
}
|
||||
|
||||
let body = ErrorBody::Standard {
|
||||
kind: error.kind(),
|
||||
message: error.message(),
|
||||
};
|
||||
let body = ErrorBody::Standard(StandardErrorBody::new(error.kind(), error.message()));
|
||||
|
||||
Self::MatrixError(ruma::api::client::error::Error {
|
||||
status_code: error.status_code(),
|
||||
body,
|
||||
})
|
||||
Self::MatrixError(ruma::api::client::error::Error::new(error.status_code(), body))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +79,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
|
||||
|
||||
// 404
|
||||
| NotFound | NotImplemented | FeatureDisabled | SenderIgnored { .. } =>
|
||||
| NotFound =>
|
||||
StatusCode::NOT_FOUND,
|
||||
|
||||
// 403
|
||||
@@ -93,7 +87,6 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
| ThreepidAuthFailed
|
||||
| UserDeactivated
|
||||
| ThreepidDenied
|
||||
| InviteBlocked
|
||||
| WrongRoomKeysVersion { .. }
|
||||
| UserSuspended
|
||||
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
||||
@@ -108,7 +101,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
}
|
||||
|
||||
pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> String {
|
||||
if let ErrorBody::Standard { message, .. } = &error.body {
|
||||
if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body {
|
||||
return message.clone();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::iter::once;
|
||||
|
||||
use ruma::{RoomVersionId, api::client::discovery::get_capabilities::RoomVersionStability};
|
||||
use ruma::{RoomVersionId, api::client::discovery::get_capabilities::v3::RoomVersionStability};
|
||||
|
||||
use crate::{at, is_equal_to};
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ fn matches_sender<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
|
||||
}
|
||||
|
||||
fn matches_type<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
|
||||
let kind = event.kind().to_cow_str();
|
||||
let kind = event.kind().to_string();
|
||||
|
||||
if filter.not_types.iter().any(is_equal_to!(&kind)) {
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruma::{CanonicalJsonObject, OwnedEventId, RoomVersionId};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
use crate::{Result, err};
|
||||
use crate::{Err, Result, err};
|
||||
|
||||
/// Generates a correct eventId for the incoming pdu.
|
||||
///
|
||||
@@ -24,7 +24,10 @@ pub fn gen_event_id(
|
||||
value: &CanonicalJsonObject,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> Result<OwnedEventId> {
|
||||
let reference_hash = ruma::signatures::reference_hash(value, room_version_id)?;
|
||||
let Some(rules) = room_version_id.rules() else {
|
||||
return Err!("Cannot generate event ID for unknown room version {room_version_id}")
|
||||
};
|
||||
let reference_hash = ruma::signatures::reference_hash(value, &rules)?;
|
||||
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
|
||||
|
||||
Ok(event_id)
|
||||
|
||||
@@ -21,12 +21,12 @@ fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, Stat
|
||||
|
||||
impl TypeExt for TimelineEventType {
|
||||
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
|
||||
(self.into(), state_key.into())
|
||||
(self.to_string().into(), state_key.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeExt for &TimelineEventType {
|
||||
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
|
||||
(self.clone().into(), state_key.into())
|
||||
(self.clone().to_string().into(), state_key.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
pub mod pdu;
|
||||
pub mod state_key;
|
||||
pub mod state_res;
|
||||
pub mod versions;
|
||||
|
||||
pub use event::{Event, TypeExt as EventTypeExt};
|
||||
pub use pdu::{Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId};
|
||||
pub use state_key::StateKey;
|
||||
pub use state_res::{RoomVersion, StateMap, TypeStateKey};
|
||||
pub use state_res::{StateMap, TypeStateKey};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId,
|
||||
events::{EventContent, MessageLikeEventType, StateEventType, TimelineEventType},
|
||||
events::{MessageLikeEventContent, StateEventContent, TimelineEventType},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
|
||||
@@ -33,7 +33,7 @@ pub struct Builder {
|
||||
impl Builder {
|
||||
pub fn state<S, T>(state_key: S, content: &T) -> Self
|
||||
where
|
||||
T: EventContent<EventType = StateEventType>,
|
||||
T: StateEventContent,
|
||||
S: Into<StateKey>,
|
||||
{
|
||||
Self {
|
||||
@@ -47,7 +47,7 @@ pub fn state<S, T>(state_key: S, content: &T) -> Self
|
||||
|
||||
pub fn timeline<T>(content: &T) -> Self
|
||||
where
|
||||
T: EventContent<EventType = MessageLikeEventType>,
|
||||
T: MessageLikeEventContent,
|
||||
{
|
||||
Self {
|
||||
event_type: content.event_type().into(),
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
|
||||
use serde_json::{Value as JsonValue, json, value::to_raw_value};
|
||||
|
||||
use crate::{Error, Result, err, implement};
|
||||
use crate::{Err, Error, Result, err, implement};
|
||||
|
||||
#[implement(super::Pdu)]
|
||||
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
|
||||
let Some(rules) = room_version_id.rules() else {
|
||||
return Err!("Cannot redact event for unknown room version {room_version_id}")
|
||||
};
|
||||
|
||||
self.unsigned = None;
|
||||
|
||||
let mut content = serde_json::from_str(self.content.get())
|
||||
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
|
||||
|
||||
redact_content_in_place(&mut content, room_version_id, self.kind.to_string())
|
||||
redact_content_in_place(&mut content, &rules.redaction, self.kind.to_string())
|
||||
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?;
|
||||
|
||||
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
|
||||
|
||||
@@ -318,7 +318,7 @@ fn set_up(
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().clone().into(), ev.state_key().unwrap().into()),
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
@@ -328,7 +328,7 @@ fn set_up(
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().clone().into(), ev.state_key().unwrap().into()),
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
@@ -338,7 +338,7 @@ fn set_up(
|
||||
.iter()
|
||||
.map(|ev| {
|
||||
(
|
||||
(ev.event_type().clone().into(), ev.state_key().unwrap().into()),
|
||||
(ev.event_type().to_string().into(), ev.state_key().unwrap().into()),
|
||||
ev.event_id().to_owned(),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -5,16 +5,12 @@
|
||||
future::{OptionFuture, join, join3},
|
||||
};
|
||||
use ruma::{
|
||||
Int, OwnedUserId, RoomVersionId, UserId,
|
||||
events::room::{
|
||||
Int, OwnedUserId, RoomVersionId, UserId, events::room::{
|
||||
create::RoomCreateEventContent,
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, ThirdPartyInvite},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
third_party_invite::RoomThirdPartyInviteEventContent,
|
||||
},
|
||||
int,
|
||||
serde::{Base64, Raw},
|
||||
}, int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules}, serde::Raw,
|
||||
};
|
||||
use serde::{
|
||||
Deserialize,
|
||||
@@ -28,7 +24,6 @@
|
||||
deserialize_power_levels, deserialize_power_levels_content_fields,
|
||||
deserialize_power_levels_content_invite, deserialize_power_levels_content_redact,
|
||||
},
|
||||
room_version::RoomVersion,
|
||||
};
|
||||
use crate::{debug, error, trace, warn};
|
||||
|
||||
@@ -65,13 +60,13 @@ pub fn auth_types_for_event(
|
||||
sender: &UserId,
|
||||
state_key: Option<&str>,
|
||||
content: &RawJsonValue,
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> serde_json::Result<Vec<(StateEventType, StateKey)>> {
|
||||
if kind == &TimelineEventType::RoomCreate {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut auth_types = if room_version.room_ids_as_hashes {
|
||||
let mut auth_types = if room_version.room_id_format == RoomIdFormatVersion::V2 {
|
||||
vec![
|
||||
(StateEventType::RoomPowerLevels, StateKey::new()),
|
||||
(StateEventType::RoomMember, sender.as_str().into()),
|
||||
@@ -120,15 +115,16 @@ struct RoomMemberContentFields {
|
||||
auth_types.push(key);
|
||||
}
|
||||
|
||||
if membership == MembershipState::Invite {
|
||||
if let Some(Ok(t_id)) = content.third_party_invite.map(|t| t.deserialize()) {
|
||||
let key =
|
||||
(StateEventType::RoomThirdPartyInvite, t_id.signed.token.into());
|
||||
if !auth_types.contains(&key) {
|
||||
auth_types.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: restore this once 3pid support isn't broken
|
||||
// if membership == MembershipState::Invite {
|
||||
// if let Some(Ok(t_id)) = content.third_party_invite.map(|t| t.deserialize()) {
|
||||
// let key =
|
||||
// (StateEventType::RoomThirdPartyInvite, t_id.signed.token.into());
|
||||
// if !auth_types.contains(&key) {
|
||||
// auth_types.push(key);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +151,7 @@ struct RoomMemberContentFields {
|
||||
)]
|
||||
#[allow(clippy::suspicious_operation_groupings)]
|
||||
pub async fn auth_check<E, F, Fut>(
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
incoming_event: &E,
|
||||
current_third_party_invite: Option<&E>,
|
||||
fetch_state: F,
|
||||
@@ -218,13 +214,13 @@ pub async fn auth_check<E, F, Fut>(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if room_version.room_ids_as_hashes && incoming_event.room_id().is_some() {
|
||||
if room_version.room_id_format == RoomIdFormatVersion::V2 && incoming_event.room_id().is_some() {
|
||||
warn!("room create event incorrectly claims to have a room ID when it should not");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !room_version.use_room_create_sender
|
||||
&& !room_version.explicitly_privilege_room_creators
|
||||
if !room_version.authorization.use_room_create_sender
|
||||
&& !room_version.authorization.explicitly_privilege_room_creators
|
||||
{
|
||||
// If content has no creator field, reject
|
||||
if content.creator.is_none() {
|
||||
@@ -305,10 +301,10 @@ pub async fn auth_check<E, F, Fut>(
|
||||
let claims_create_event = incoming_event
|
||||
.auth_events()
|
||||
.any(|id| id == room_create_event.event_id());
|
||||
if room_version.room_ids_as_hashes && claims_create_event {
|
||||
if room_version.room_id_format == RoomIdFormatVersion::V2 && claims_create_event {
|
||||
warn!("event incorrectly references m.room.create event in auth events");
|
||||
return Ok(false);
|
||||
} else if !room_version.room_ids_as_hashes && !claims_create_event {
|
||||
} else if !(room_version.room_id_format == RoomIdFormatVersion::V2) && !claims_create_event {
|
||||
// If the create event is not referenced in the event's auth events, and this is
|
||||
// a v11 room, reject
|
||||
warn!(
|
||||
@@ -332,7 +328,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
// If the create event content has the field m.federate set to false and the
|
||||
// sender domain of the event does not match the sender domain of the create
|
||||
// event, reject.
|
||||
if !room_version.room_ids_as_hashes
|
||||
if !(room_version.room_id_format == RoomIdFormatVersion::V2)
|
||||
&& !room_create_content.federate
|
||||
&& room_create_event.sender().server_name() != incoming_event.sender().server_name()
|
||||
{
|
||||
@@ -345,7 +341,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
}
|
||||
|
||||
// Only in some room versions 6 and below
|
||||
if room_version.special_case_aliases_auth {
|
||||
if room_version.authorization.special_case_room_aliases {
|
||||
// 4. If type is m.room.aliases
|
||||
if *incoming_event.event_type() == TimelineEventType::RoomAliases {
|
||||
debug!("starting m.room.aliases check");
|
||||
@@ -486,7 +482,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
},
|
||||
| _ => {
|
||||
// If no power level event found the creator gets 100 everyone else gets 0
|
||||
let is_creator = if room_version.use_room_create_sender {
|
||||
let is_creator = if room_version.authorization.use_room_create_sender {
|
||||
room_create_event.sender() == sender
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
@@ -497,7 +493,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
if is_creator { int!(100) } else { int!(0) }
|
||||
},
|
||||
};
|
||||
if room_version.explicitly_privilege_room_creators {
|
||||
if room_version.authorization.explicitly_privilege_room_creators {
|
||||
// If the user sent the create event, or is listed in additional_creators, just
|
||||
// give them Int::MAX
|
||||
if sender == room_create_event.sender()
|
||||
@@ -559,7 +555,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
|
||||
debug!("starting m.room.power_levels check");
|
||||
let mut creators = BTreeSet::new();
|
||||
if room_version.explicitly_privilege_room_creators {
|
||||
if room_version.authorization.explicitly_privilege_room_creators {
|
||||
creators.insert(create_event.sender().to_owned());
|
||||
for creator in room_create_content.additional_creators.iter().flatten() {
|
||||
creators.insert(creator.deserialize()?);
|
||||
@@ -593,7 +589,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
// the sender of the redaction has the appropriate permissions per the
|
||||
// power levels.
|
||||
|
||||
if room_version.extra_redaction_checks
|
||||
if room_version.authorization.special_case_room_redaction
|
||||
&& *incoming_event.event_type() == TimelineEventType::RoomRedaction
|
||||
{
|
||||
let redact_level = match power_levels_event {
|
||||
@@ -618,7 +614,7 @@ pub async fn auth_check<E, F, Fut>(
|
||||
}
|
||||
|
||||
fn is_creator<EV>(
|
||||
v: &RoomVersion,
|
||||
v: &RoomVersionRules,
|
||||
c: &BTreeSet<OwnedUserId>,
|
||||
ce: &EV,
|
||||
user_id: &UserId,
|
||||
@@ -627,9 +623,9 @@ fn is_creator<EV>(
|
||||
where
|
||||
EV: Event + Send + Sync,
|
||||
{
|
||||
if v.explicitly_privilege_room_creators {
|
||||
if v.authorization.explicitly_privilege_room_creators {
|
||||
c.contains(user_id)
|
||||
} else if v.use_room_create_sender && !have_pls {
|
||||
} else if v.authorization.use_room_create_sender && !have_pls {
|
||||
ce.sender() == user_id
|
||||
} else if !have_pls {
|
||||
#[allow(deprecated)]
|
||||
@@ -659,7 +655,7 @@ fn is_creator<EV>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn valid_membership_change<E>(
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
target_user: &UserId,
|
||||
target_user_membership_event: Option<&E>,
|
||||
sender: &UserId,
|
||||
@@ -700,7 +696,7 @@ struct GetThirdPartyInvite {
|
||||
|
||||
let power_levels: RoomPowerLevelsEventContent = match &power_levels_event {
|
||||
| Some(ev) => from_json_str(ev.content().get())?,
|
||||
| None => RoomPowerLevelsEventContent::default(),
|
||||
| None => RoomPowerLevelsEventContent::new(&room_version.authorization),
|
||||
};
|
||||
|
||||
let mut sender_power = power_levels
|
||||
@@ -714,7 +710,7 @@ struct GetThirdPartyInvite {
|
||||
|
||||
let mut creators = BTreeSet::new();
|
||||
creators.insert(create_room.sender().to_owned());
|
||||
if room_version.explicitly_privilege_room_creators {
|
||||
if room_version.authorization.explicitly_privilege_room_creators {
|
||||
// Explicitly privilege room creators
|
||||
// If the sender sent the create event, or in additional_creators, give them
|
||||
// Int::MAX. Same case for target.
|
||||
@@ -865,7 +861,7 @@ struct GetThirdPartyInvite {
|
||||
trace!(sender=%sender, "sender is invited to room, allowing join");
|
||||
true
|
||||
},
|
||||
| JoinRule::Knock if !room_version.allow_knocking => {
|
||||
| JoinRule::Knock if !room_version.authorization.knocking => {
|
||||
warn!("Join rule is knock but room version does not allow knocking");
|
||||
false
|
||||
},
|
||||
@@ -882,7 +878,7 @@ struct GetThirdPartyInvite {
|
||||
trace!(sender=%sender, "sender is invited or already joined to room, allowing join");
|
||||
true
|
||||
},
|
||||
| JoinRule::KnockRestricted(_) if !room_version.knock_restricted_join_rule =>
|
||||
| JoinRule::KnockRestricted(_) if !room_version.authorization.knock_restricted_join_rule =>
|
||||
{
|
||||
warn!(
|
||||
"Join rule is knock_restricted but room version does not support it"
|
||||
@@ -1127,7 +1123,7 @@ struct GetThirdPartyInvite {
|
||||
}
|
||||
allow
|
||||
},
|
||||
| MembershipState::Knock if room_version.allow_knocking => {
|
||||
| MembershipState::Knock if room_version.authorization.knocking => {
|
||||
// 1. If the `join_rule` is anything other than `knock` or `knock_restricted`,
|
||||
// reject.
|
||||
if !matches!(join_rules, JoinRule::KnockRestricted(_) | JoinRule::Knock) {
|
||||
@@ -1136,7 +1132,7 @@ struct GetThirdPartyInvite {
|
||||
);
|
||||
false
|
||||
} else if matches!(join_rules, JoinRule::KnockRestricted(_))
|
||||
&& !room_version.knock_restricted_join_rule
|
||||
&& !room_version.authorization.knock_restricted_join_rule
|
||||
{
|
||||
// 2. If the `join_rule` is `knock_restricted`, but the room does not support
|
||||
// `knock_restricted`, reject.
|
||||
@@ -1226,7 +1222,7 @@ fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int)
|
||||
/// Confirm that the event sender has the required power levels.
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn check_power_levels(
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
power_event: &impl Event,
|
||||
previous_power_event: Option<&impl Event>,
|
||||
user_level: Int,
|
||||
@@ -1382,7 +1378,7 @@ fn check_power_levels(
|
||||
}
|
||||
|
||||
// Notifications, currently there is only @room
|
||||
if room_version.limit_notifications_power_levels {
|
||||
if room_version.authorization.limit_notifications_power_levels {
|
||||
let old_level = old_state.notifications.room;
|
||||
let new_level = new_state.notifications.room;
|
||||
if old_level != new_level {
|
||||
@@ -1451,7 +1447,7 @@ fn get_deserialize_levels(
|
||||
/// Does the event redacting come from a user with enough power to redact the
|
||||
/// given event.
|
||||
fn check_redaction(
|
||||
_room_version: &RoomVersion,
|
||||
_room_version: &RoomVersionRules,
|
||||
redaction_event: &impl Event,
|
||||
user_level: Int,
|
||||
redact_level: Int,
|
||||
@@ -1501,79 +1497,32 @@ fn get_send_level(
|
||||
}
|
||||
|
||||
fn verify_third_party_invite(
|
||||
target_user: Option<&UserId>,
|
||||
sender: &UserId,
|
||||
tp_id: &ThirdPartyInvite,
|
||||
current_third_party_invite: Option<&impl Event>,
|
||||
_target_user: Option<&UserId>,
|
||||
_sender: &UserId,
|
||||
_tp_id: &ThirdPartyInvite,
|
||||
_current_third_party_invite: Option<&impl Event>,
|
||||
) -> bool {
|
||||
// 1. Check for user being banned happens before this is called
|
||||
// checking for mxid and token keys is done by ruma when deserializing
|
||||
|
||||
// The state key must match the invitee
|
||||
if target_user != Some(&tp_id.signed.mxid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no m.room.third_party_invite event in the current room state with
|
||||
// state_key matching token, reject
|
||||
#[allow(clippy::manual_let_else)]
|
||||
let current_tpid = match current_third_party_invite {
|
||||
| Some(id) => id,
|
||||
| None => return false,
|
||||
};
|
||||
|
||||
if current_tpid.state_key() != Some(&tp_id.signed.token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if sender != current_tpid.sender() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If any signature in signed matches any public key in the
|
||||
// m.room.third_party_invite event, allow
|
||||
#[allow(clippy::manual_let_else)]
|
||||
let tpid_ev =
|
||||
match from_json_str::<RoomThirdPartyInviteEventContent>(current_tpid.content().get()) {
|
||||
| Ok(ev) => ev,
|
||||
| Err(_) => return false,
|
||||
};
|
||||
|
||||
#[allow(clippy::manual_let_else)]
|
||||
let decoded_invite_token = match Base64::parse(&tp_id.signed.token) {
|
||||
| Ok(tok) => tok,
|
||||
// FIXME: Log a warning?
|
||||
| Err(_) => return false,
|
||||
};
|
||||
|
||||
// A list of public keys in the public_keys field
|
||||
for key in tpid_ev.public_keys.unwrap_or_default() {
|
||||
if key.public_key == decoded_invite_token {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// A single public key in the public_key field
|
||||
tpid_ev.public_key == decoded_invite_token
|
||||
// TODO: implement proper verification here
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::events::{
|
||||
use ruma::{events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
join_rules::{
|
||||
AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent, RoomMembership,
|
||||
AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent,
|
||||
},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
}, room::RoomMembership, room_version_rules::RoomVersionRules};
|
||||
use serde_json::value::to_raw_value as to_raw_json_value;
|
||||
|
||||
use crate::{
|
||||
matrix::{Event, EventTypeExt, Pdu as PduEvent},
|
||||
state_res::{
|
||||
RoomVersion, StateMap,
|
||||
StateMap,
|
||||
event_auth::valid_membership_change,
|
||||
test_utils::{
|
||||
INITIAL_EVENTS, INITIAL_EVENTS_CREATE_ROOM, alice, charlie, ella, event_id,
|
||||
@@ -1610,7 +1559,7 @@ fn test_ban_pass() {
|
||||
|
||||
assert!(
|
||||
valid_membership_change(
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1655,7 +1604,7 @@ fn test_join_non_creator() {
|
||||
|
||||
assert!(
|
||||
!valid_membership_change(
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1700,7 +1649,7 @@ fn test_join_creator() {
|
||||
|
||||
assert!(
|
||||
valid_membership_change(
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1745,7 +1694,7 @@ fn test_ban_fail() {
|
||||
|
||||
assert!(
|
||||
!valid_membership_change(
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1807,7 +1756,7 @@ fn test_restricted_join_rule() {
|
||||
|
||||
assert!(
|
||||
valid_membership_change(
|
||||
&RoomVersion::V9,
|
||||
&RoomVersionRules::V9,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1825,7 +1774,7 @@ fn test_restricted_join_rule() {
|
||||
|
||||
assert!(
|
||||
!valid_membership_change(
|
||||
&RoomVersion::V9,
|
||||
&RoomVersionRules::V9,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
@@ -1879,7 +1828,7 @@ fn test_knock() {
|
||||
|
||||
assert!(
|
||||
valid_membership_change(
|
||||
&RoomVersion::V7,
|
||||
&RoomVersionRules::V7,
|
||||
target_user,
|
||||
fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(),
|
||||
sender,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
pub(crate) mod error;
|
||||
pub mod event_auth;
|
||||
mod power_levels;
|
||||
mod room_version;
|
||||
mod serde_backports;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
@@ -20,25 +20,21 @@
|
||||
|
||||
use futures::{Future, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future};
|
||||
use ruma::{
|
||||
EventId, Int, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId,
|
||||
EventId, Int, MilliSecondsSinceUnixEpoch, OwnedEventId,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
int,
|
||||
int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules, StateResolutionVersion},
|
||||
};
|
||||
use serde_json::from_str as from_json_str;
|
||||
|
||||
pub(crate) use self::error::Error;
|
||||
use self::power_levels::PowerLevelsContentFields;
|
||||
pub use self::{
|
||||
event_auth::{auth_check, auth_types_for_event},
|
||||
room_version::RoomVersion,
|
||||
};
|
||||
pub use self::event_auth::{auth_check, auth_types_for_event};
|
||||
use crate::{
|
||||
debug, debug_error, err,
|
||||
matrix::{Event, StateKey},
|
||||
state_res::room_version::StateResolutionVersion,
|
||||
trace,
|
||||
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt},
|
||||
warn,
|
||||
@@ -77,7 +73,7 @@
|
||||
//#[tracing::instrument(level event_fetch))]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>(
|
||||
room_version: &RoomVersionId,
|
||||
room_version: &RoomVersionRules,
|
||||
state_sets: Sets,
|
||||
auth_chain_sets: &'a [HashSet<OwnedEventId, Hasher>],
|
||||
event_fetch: &Fetch,
|
||||
@@ -94,11 +90,7 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
|
||||
Pdu: Event + Clone + Send + Sync,
|
||||
for<'b> &'b Pdu: Event + Send,
|
||||
{
|
||||
use RoomVersionId::*;
|
||||
let stateres_version = match room_version {
|
||||
| V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 => StateResolutionVersion::V2,
|
||||
| _ => StateResolutionVersion::V2_1,
|
||||
};
|
||||
let stateres_version = room_version.state_res;
|
||||
debug!(version = ?stateres_version, "State resolution starting");
|
||||
|
||||
// Split non-conflicting and conflicting state
|
||||
@@ -115,7 +107,7 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
|
||||
debug!(count = conflicting.len(), "conflicting events");
|
||||
trace!(map = ?conflicting, "conflicting events");
|
||||
let (conflicted_state_subgraph, initial_state) =
|
||||
if stateres_version == StateResolutionVersion::V2_1 {
|
||||
if let StateResolutionVersion::V2(v2_rules) = stateres_version && v2_rules.consider_conflicted_state_subgraph {
|
||||
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
@@ -166,7 +158,6 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
|
||||
debug!(count = sorted_control_levels.len(), "power events");
|
||||
trace!(list = ?sorted_control_levels, "sorted power events");
|
||||
|
||||
let room_version = RoomVersion::new(room_version)?;
|
||||
// Sequentially auth check each control event.
|
||||
let resolved_control = iterative_auth_check(
|
||||
&room_version,
|
||||
@@ -596,7 +587,7 @@ async fn get_power_level_for_sender<E, F, Fut>(
|
||||
/// `event_auth::auth_check` function.
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
events_to_check: S,
|
||||
unconflicted_state: StateMap<OwnedEventId>,
|
||||
fetch_event: &F,
|
||||
@@ -666,7 +657,7 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
|
||||
trace!(list = ?auth_types, event_id = event.event_id().as_str(), "auth types for event");
|
||||
|
||||
let mut auth_state = StateMap::new();
|
||||
if room_version.room_ids_as_hashes {
|
||||
if room_version.room_id_format == RoomIdFormatVersion::V2 {
|
||||
trace!("room version uses hashed IDs, manually fetching create event");
|
||||
let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$");
|
||||
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
|
||||
@@ -963,7 +954,7 @@ fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, Stat
|
||||
|
||||
impl EventTypeExt for TimelineEventType {
|
||||
fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, StateKey) {
|
||||
(self.into(), state_key.into())
|
||||
(self.to_string().into(), state_key.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,18 +974,15 @@ mod tests {
|
||||
use maplit::{hashmap, hashset};
|
||||
use rand::seq::SliceRandom;
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId,
|
||||
events::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId, events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
},
|
||||
int, uint,
|
||||
}, int, room_version_rules::RoomVersionRules, uint
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value as to_raw_json_value};
|
||||
|
||||
use super::{
|
||||
StateMap, is_power_event,
|
||||
room_version::RoomVersion,
|
||||
test_utils::{
|
||||
INITIAL_EVENTS, TestStore, alice, bob, charlie, do_check, ella, event_id,
|
||||
member_content_ban, member_content_join, room_id, to_init_pdu_event, to_pdu_event,
|
||||
@@ -1004,7 +992,6 @@ mod tests {
|
||||
use crate::{
|
||||
debug,
|
||||
matrix::{Event, EventTypeExt, Pdu as PduEvent},
|
||||
state_res::room_version::StateResolutionVersion,
|
||||
utils::stream::IterStream,
|
||||
};
|
||||
|
||||
@@ -1036,7 +1023,7 @@ async fn test_event_sort() {
|
||||
.unwrap();
|
||||
|
||||
let resolved_power = super::iterative_auth_check(
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
sorted_power_events.iter().map(AsRef::as_ref).stream(),
|
||||
HashMap::new(), // unconflicted events
|
||||
&fetcher,
|
||||
@@ -1437,7 +1424,7 @@ async fn test_event_map_none() {
|
||||
.collect();
|
||||
|
||||
let resolved =
|
||||
match super::resolve(&RoomVersionId::V2, &state_sets, &auth_chain, &fetcher, &exists)
|
||||
match super::resolve(&RoomVersionRules::V2, &state_sets, &auth_chain, &fetcher, &exists)
|
||||
.await
|
||||
{
|
||||
| Ok(state) => state,
|
||||
@@ -1550,7 +1537,7 @@ async fn ban_with_auth_chains2() {
|
||||
let fetcher = |id: OwnedEventId| ready(ev_map.get(&id).cloned());
|
||||
let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some());
|
||||
let resolved =
|
||||
match super::resolve(&RoomVersionId::V6, &state_sets, &auth_chain, &fetcher, &exists)
|
||||
match super::resolve(&RoomVersionRules::V6, &state_sets, &auth_chain, &fetcher, &exists)
|
||||
.await
|
||||
{
|
||||
| Ok(state) => state,
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
Int, OwnedUserId, UserId,
|
||||
events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent},
|
||||
power_levels::{NotificationPowerLevels, default_power_level},
|
||||
serde::{
|
||||
deserialize_v1_powerlevel, vec_deserialize_int_powerlevel_values,
|
||||
vec_deserialize_v1_powerlevel_values,
|
||||
},
|
||||
Int, OwnedUserId, UserId, events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent}, power_levels::{NotificationPowerLevels, default_power_level}, room_version_rules::{AuthorizationRules, RoomVersionRules}, serde::deserialize_v1_powerlevel
|
||||
};
|
||||
use super::serde_backports::*;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Error, from_str as from_json_str};
|
||||
|
||||
use super::{Result, RoomVersion};
|
||||
use super::Result;
|
||||
use crate::error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -48,8 +43,8 @@ struct IntRoomPowerLevelsEventContent {
|
||||
notifications: IntNotificationPowerLevels,
|
||||
}
|
||||
|
||||
impl From<IntRoomPowerLevelsEventContent> for RoomPowerLevelsEventContent {
|
||||
fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self {
|
||||
impl IntRoomPowerLevelsEventContent {
|
||||
fn to_room_power_levels_content(self, auth_rules: &AuthorizationRules) -> RoomPowerLevelsEventContent {
|
||||
let IntRoomPowerLevelsEventContent {
|
||||
ban,
|
||||
events,
|
||||
@@ -61,9 +56,9 @@ fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self {
|
||||
users,
|
||||
users_default,
|
||||
notifications,
|
||||
} = int_pl;
|
||||
} = self;
|
||||
|
||||
let mut pl = Self::new();
|
||||
let mut pl = RoomPowerLevelsEventContent::new(auth_rules);
|
||||
pl.ban = ban;
|
||||
pl.events = events;
|
||||
pl.events_default = events_default;
|
||||
@@ -101,18 +96,18 @@ fn from(int_notif: IntNotificationPowerLevels) -> Self {
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_power_levels(
|
||||
content: &str,
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Option<RoomPowerLevelsEventContent> {
|
||||
if room_version.integer_power_levels {
|
||||
deserialize_integer_power_levels(content)
|
||||
if room_version.authorization.integer_power_levels {
|
||||
deserialize_integer_power_levels(content, &room_version.authorization)
|
||||
} else {
|
||||
deserialize_legacy_power_levels(content)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_integer_power_levels(content: &str) -> Option<RoomPowerLevelsEventContent> {
|
||||
fn deserialize_integer_power_levels(content: &str, auth_rules: &AuthorizationRules) -> Option<RoomPowerLevelsEventContent> {
|
||||
match from_json_str::<IntRoomPowerLevelsEventContent>(content) {
|
||||
| Ok(content) => Some(content.into()),
|
||||
| Ok(content) => Some(content.to_room_power_levels_content(auth_rules)),
|
||||
| Err(_) => {
|
||||
error!("m.room.power_levels event is not valid with integer values");
|
||||
None
|
||||
@@ -174,9 +169,9 @@ fn from(pl: IntPowerLevelsContentFields) -> Self {
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_power_levels_content_fields(
|
||||
content: &str,
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Result<PowerLevelsContentFields, Error> {
|
||||
if room_version.integer_power_levels {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
deserialize_integer_power_levels_content_fields(content)
|
||||
} else {
|
||||
deserialize_legacy_power_levels_content_fields(content)
|
||||
@@ -216,9 +211,9 @@ fn from(pl: IntPowerLevelsContentInvite) -> Self {
|
||||
|
||||
pub(crate) fn deserialize_power_levels_content_invite(
|
||||
content: &str,
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Result<PowerLevelsContentInvite, Error> {
|
||||
if room_version.integer_power_levels {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
from_json_str::<IntPowerLevelsContentInvite>(content).map(Into::into)
|
||||
} else {
|
||||
from_json_str(content)
|
||||
@@ -246,9 +241,9 @@ fn from(pl: IntPowerLevelsContentRedact) -> Self {
|
||||
|
||||
pub(crate) fn deserialize_power_levels_content_redact(
|
||||
content: &str,
|
||||
room_version: &RoomVersion,
|
||||
room_version: &RoomVersionRules,
|
||||
) -> Result<PowerLevelsContentRedact, Error> {
|
||||
if room_version.integer_power_levels {
|
||||
if room_version.authorization.integer_power_levels {
|
||||
from_json_str::<IntPowerLevelsContentRedact>(content).map(Into::into)
|
||||
} else {
|
||||
from_json_str(content)
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
use ruma::RoomVersionId;
|
||||
|
||||
use super::{Error, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::exhaustive_enums)]
|
||||
pub enum RoomDisposition {
|
||||
/// A room version that has a stable specification.
|
||||
Stable,
|
||||
/// A room version that is not yet fully specified.
|
||||
Unstable,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub enum EventFormatVersion {
|
||||
/// $id:server event id format
|
||||
V1,
|
||||
/// MSC1659-style $hash event id format: introduced for room v3
|
||||
V2,
|
||||
/// MSC1884-style $hash format: introduced for room v4
|
||||
V3,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub enum StateResolutionVersion {
|
||||
/// State resolution for rooms at version 1.
|
||||
V1,
|
||||
/// State resolution for room at version 2 or later.
|
||||
V2,
|
||||
/// State resolution for room at version 12 or later.
|
||||
V2_1,
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct RoomVersion {
|
||||
/// The stability of this room.
|
||||
pub disposition: RoomDisposition,
|
||||
/// The format of the EventId.
|
||||
pub event_format: EventFormatVersion,
|
||||
/// Which state resolution algorithm is used.
|
||||
pub state_res: StateResolutionVersion,
|
||||
// FIXME: not sure what this one means?
|
||||
pub enforce_key_validity: bool,
|
||||
|
||||
/// `m.room.aliases` had special auth rules and redaction rules
|
||||
/// before room version 6.
|
||||
///
|
||||
/// before MSC2261/MSC2432,
|
||||
pub special_case_aliases_auth: bool,
|
||||
/// Strictly enforce canonical json, do not allow:
|
||||
/// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
|
||||
/// * Floats
|
||||
/// * NaN, Infinity, -Infinity
|
||||
pub strict_canonicaljson: bool,
|
||||
/// Verify notifications key while checking m.room.power_levels.
|
||||
///
|
||||
/// bool: MSC2209: Check 'notifications'
|
||||
pub limit_notifications_power_levels: bool,
|
||||
/// Extra rules when verifying redaction events.
|
||||
pub extra_redaction_checks: bool,
|
||||
/// Allow knocking in event authentication.
|
||||
///
|
||||
/// See [room v7 specification](https://spec.matrix.org/latest/rooms/v7/)
|
||||
pub allow_knocking: bool,
|
||||
/// Adds support for the restricted join rule.
|
||||
///
|
||||
/// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289)
|
||||
pub restricted_join_rules: bool,
|
||||
/// Adds support for the knock_restricted join rule.
|
||||
///
|
||||
/// See: [MSC3787](https://github.com/matrix-org/matrix-spec-proposals/pull/3787)
|
||||
pub knock_restricted_join_rule: bool,
|
||||
/// Enforces integer power levels.
|
||||
///
|
||||
/// See: [MSC3667](https://github.com/matrix-org/matrix-spec-proposals/pull/3667)
|
||||
pub integer_power_levels: bool,
|
||||
/// Determine the room creator using the `m.room.create` event's `sender`,
|
||||
/// instead of the event content's `creator` field.
|
||||
///
|
||||
/// See: [MSC2175](https://github.com/matrix-org/matrix-spec-proposals/pull/2175)
|
||||
pub use_room_create_sender: bool,
|
||||
/// Whether the room creators are considered superusers.
|
||||
/// A superuser will always have infinite power levels in the room.
|
||||
///
|
||||
/// See: [MSC4289](https://github.com/matrix-org/matrix-spec-proposals/pull/4289)
|
||||
pub explicitly_privilege_room_creators: bool,
|
||||
/// Whether the room's m.room.create event ID is itself the room ID.
|
||||
///
|
||||
/// See: [MSC4291](https://github.com/matrix-org/matrix-spec-proposals/pull/4291)
|
||||
pub room_ids_as_hashes: bool,
|
||||
}
|
||||
|
||||
impl RoomVersion {
|
||||
pub const V1: Self = Self {
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V1,
|
||||
state_res: StateResolutionVersion::V1,
|
||||
enforce_key_validity: false,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
extra_redaction_checks: true,
|
||||
allow_knocking: false,
|
||||
restricted_join_rules: false,
|
||||
knock_restricted_join_rule: false,
|
||||
integer_power_levels: false,
|
||||
use_room_create_sender: false,
|
||||
explicitly_privilege_room_creators: false,
|
||||
room_ids_as_hashes: false,
|
||||
};
|
||||
pub const V10: Self = Self {
|
||||
knock_restricted_join_rule: true,
|
||||
integer_power_levels: true,
|
||||
..Self::V9
|
||||
};
|
||||
pub const V11: Self = Self {
|
||||
use_room_create_sender: true,
|
||||
..Self::V10
|
||||
};
|
||||
pub const V12: Self = Self {
|
||||
explicitly_privilege_room_creators: true,
|
||||
room_ids_as_hashes: true,
|
||||
..Self::V11
|
||||
};
|
||||
pub const V2: Self = Self {
|
||||
state_res: StateResolutionVersion::V2,
|
||||
..Self::V1
|
||||
};
|
||||
pub const V3: Self = Self {
|
||||
event_format: EventFormatVersion::V2,
|
||||
extra_redaction_checks: false,
|
||||
..Self::V2
|
||||
};
|
||||
pub const V4: Self = Self {
|
||||
event_format: EventFormatVersion::V3,
|
||||
..Self::V3
|
||||
};
|
||||
pub const V5: Self = Self { enforce_key_validity: true, ..Self::V4 };
|
||||
pub const V6: Self = Self {
|
||||
special_case_aliases_auth: false,
|
||||
strict_canonicaljson: true,
|
||||
limit_notifications_power_levels: true,
|
||||
..Self::V5
|
||||
};
|
||||
pub const V7: Self = Self { allow_knocking: true, ..Self::V6 };
|
||||
pub const V8: Self = Self { restricted_join_rules: true, ..Self::V7 };
|
||||
pub const V9: Self = Self::V8;
|
||||
|
||||
pub fn new(version: &RoomVersionId) -> Result<Self> {
|
||||
Ok(match version {
|
||||
| RoomVersionId::V1 => Self::V1,
|
||||
| RoomVersionId::V2 => Self::V2,
|
||||
| RoomVersionId::V3 => Self::V3,
|
||||
| RoomVersionId::V4 => Self::V4,
|
||||
| RoomVersionId::V5 => Self::V5,
|
||||
| RoomVersionId::V6 => Self::V6,
|
||||
| RoomVersionId::V7 => Self::V7,
|
||||
| RoomVersionId::V8 => Self::V8,
|
||||
| RoomVersionId::V9 => Self::V9,
|
||||
| RoomVersionId::V10 => Self::V10,
|
||||
| RoomVersionId::V11 => Self::V11,
|
||||
| RoomVersionId::V12 => Self::V12,
|
||||
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//! These functions are copied from an old version of Ruma. power_levels.rs uses them to lazily deserialize power level events.
|
||||
//! Upstream Ruma uses a much more elegant approach in its state resolution code, which we may want
|
||||
//! to look into at some point.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||
use ruma::{Int, serde::deserialize_v1_powerlevel};
|
||||
|
||||
/// Take a Map with values of either an integer number or a string and deserialize
|
||||
/// those to integer numbers in a Vec of sorted pairs.
|
||||
///
|
||||
/// To be used like this:
|
||||
/// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]`
|
||||
pub fn vec_deserialize_v1_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
#[repr(transparent)]
|
||||
struct IntWrap(Int);
|
||||
|
||||
impl<'de> Deserialize<'de> for IntWrap {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserialize_v1_powerlevel(deserializer).map(IntWrap)
|
||||
}
|
||||
}
|
||||
|
||||
struct IntMapVisitor<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IntMapVisitor<T> {
|
||||
fn new() -> Self {
|
||||
Self { _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
type Value = Vec<(T, Int)>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map with integers or strings as values")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut res = Vec::new();
|
||||
if let Some(hint) = map.size_hint() {
|
||||
res.reserve(hint);
|
||||
}
|
||||
|
||||
while let Some((k, IntWrap(v))) = map.next_entry()? {
|
||||
res.push((k, v));
|
||||
}
|
||||
|
||||
res.sort_unstable();
|
||||
res.dedup_by(|a, b| a.0 == b.0);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(IntMapVisitor::new())
|
||||
}
|
||||
|
||||
/// Take a Map with integer values and deserialize those to a Vec of sorted pairs
|
||||
///
|
||||
/// To be used like this:
|
||||
/// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]`
|
||||
pub fn vec_deserialize_int_powerlevel_values<'de, D, T>(de: D) -> Result<Vec<(T, Int)>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
struct IntMapVisitor<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IntMapVisitor<T> {
|
||||
fn new() -> Self {
|
||||
Self { _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de> + Ord,
|
||||
{
|
||||
type Value = Vec<(T, Int)>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map with integers as values")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut res = Vec::new();
|
||||
if let Some(hint) = map.size_hint() {
|
||||
res.reserve(hint);
|
||||
}
|
||||
|
||||
while let Some(item) = map.next_entry()? {
|
||||
res.push(item);
|
||||
}
|
||||
|
||||
res.sort_unstable();
|
||||
res.dedup_by(|a, b| a.0 == b.0);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(IntMapVisitor::new())
|
||||
}
|
||||
@@ -6,16 +6,13 @@
|
||||
|
||||
use futures::future::ready;
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures,
|
||||
UserId, event_id,
|
||||
events::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures, UserId, event_id, events::{
|
||||
TimelineEventType,
|
||||
room::{
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
int, room_id, uint, user_id,
|
||||
}, int, room_id, room_version_rules::RoomVersionRules, uint, user_id
|
||||
};
|
||||
use serde_json::{
|
||||
json,
|
||||
@@ -24,7 +21,7 @@
|
||||
|
||||
use super::auth_types_for_event;
|
||||
use crate::{
|
||||
Result, RoomVersion, info,
|
||||
Result, info,
|
||||
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
|
||||
};
|
||||
|
||||
@@ -134,7 +131,7 @@ pub(crate) async fn do_check(
|
||||
let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned());
|
||||
let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some());
|
||||
let resolved =
|
||||
super::resolve(&RoomVersionId::V6, state_sets, &auth_chain_sets, &fetch, &exists)
|
||||
super::resolve(&RoomVersionRules::V6, state_sets, &auth_chain_sets, &fetch, &exists)
|
||||
.await;
|
||||
|
||||
match resolved {
|
||||
@@ -154,7 +151,7 @@ pub(crate) async fn do_check(
|
||||
fake_event.sender(),
|
||||
fake_event.state_key(),
|
||||
fake_event.content(),
|
||||
&RoomVersion::V6,
|
||||
&RoomVersionRules::V6,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub fn versions() -> Vec<String> {
|
||||
vec![
|
||||
"r0.0.1".to_owned(),
|
||||
"r0.1.0".to_owned(),
|
||||
"r0.2.0".to_owned(),
|
||||
"r0.3.0".to_owned(),
|
||||
"r0.4.0".to_owned(),
|
||||
"r0.5.0".to_owned(),
|
||||
"r0.6.0".to_owned(),
|
||||
"r0.6.1".to_owned(),
|
||||
"v1.1".to_owned(),
|
||||
"v1.2".to_owned(),
|
||||
"v1.3".to_owned(),
|
||||
"v1.4".to_owned(),
|
||||
"v1.5".to_owned(),
|
||||
"v1.8".to_owned(),
|
||||
"v1.11".to_owned(),
|
||||
"v1.12".to_owned(),
|
||||
"v1.13".to_owned(),
|
||||
"v1.14".to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn unstable_features() -> BTreeMap<String, bool> {
|
||||
BTreeMap::from_iter([
|
||||
("org.matrix.e2e_cross_signing".to_owned(), true),
|
||||
("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */
|
||||
("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */
|
||||
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
|
||||
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
|
||||
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
|
||||
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
|
||||
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
|
||||
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
|
||||
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
|
||||
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
|
||||
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
|
||||
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
|
||||
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
|
||||
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
|
||||
])
|
||||
}
|
||||
+13
-13
@@ -9,6 +9,18 @@
|
||||
pub mod matrix;
|
||||
pub mod metrics;
|
||||
pub mod mods;
|
||||
|
||||
#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))]
|
||||
pub mod mods {
|
||||
#[macro_export]
|
||||
macro_rules! mod_ctor {
|
||||
() => {};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! mod_dtor {
|
||||
() => {};
|
||||
}
|
||||
}
|
||||
pub mod server;
|
||||
pub mod utils;
|
||||
|
||||
@@ -27,7 +39,7 @@
|
||||
version::{name, version},
|
||||
};
|
||||
pub use matrix::{
|
||||
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res,
|
||||
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res,
|
||||
};
|
||||
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
|
||||
pub use server::Server;
|
||||
@@ -36,15 +48,3 @@
|
||||
pub use crate as conduwuit_core;
|
||||
|
||||
conduwuit_macros::introspect_crate! {}
|
||||
|
||||
#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))]
|
||||
pub mod mods {
|
||||
#[macro_export]
|
||||
macro_rules! mod_ctor {
|
||||
() => {};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! mod_dtor {
|
||||
() => {};
|
||||
}
|
||||
}
|
||||
|
||||
+69
-111
@@ -1,10 +1,10 @@
|
||||
#![allow(clippy::needless_borrows_for_generic_args)]
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use conduwuit::{
|
||||
arrayvec::ArrayVec,
|
||||
ruma::{EventId, RoomId, UserId, serde::Raw},
|
||||
ruma::{EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, room_id, serde::Raw, user_id},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
#[test]
|
||||
#[cfg_attr(debug_assertions, should_panic(expected = "serializing string at the top-level"))]
|
||||
fn ser_str() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let s = serialize_to_vec(&user_id).expect("failed to serialize user_id");
|
||||
assert_eq!(&s, user_id.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_tuple() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let mut a = user_id.as_bytes().to_vec();
|
||||
a.push(0xFF);
|
||||
@@ -38,8 +38,8 @@ fn ser_tuple() {
|
||||
|
||||
#[test]
|
||||
fn ser_tuple_option() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut a = Vec::<u8>::new();
|
||||
a.push(0xFF);
|
||||
@@ -64,8 +64,8 @@ fn ser_tuple_option() {
|
||||
fn ser_overflow() {
|
||||
const BUFSIZE: usize = 10;
|
||||
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
assert!(BUFSIZE < user_id.as_str().len() + room_id.as_str().len());
|
||||
let mut buf = ArrayVec::<u8, BUFSIZE>::new();
|
||||
@@ -74,48 +74,12 @@ fn ser_overflow() {
|
||||
_ = ser::serialize(&mut buf, val).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_complex() {
|
||||
use conduwuit::ruma::Mxc;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Dim {
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
let mxc = Mxc {
|
||||
server_name: "example.com".try_into().unwrap(),
|
||||
media_id: "AbCdEfGhIjK",
|
||||
};
|
||||
|
||||
let dim = Dim { width: 123, height: 456 };
|
||||
|
||||
let mut a = Vec::new();
|
||||
a.extend_from_slice(b"mxc://");
|
||||
a.extend_from_slice(mxc.server_name.as_bytes());
|
||||
a.extend_from_slice(b"/");
|
||||
a.extend_from_slice(mxc.media_id.as_bytes());
|
||||
a.push(0xFF);
|
||||
a.extend_from_slice(&dim.width.to_be_bytes());
|
||||
a.extend_from_slice(&dim.height.to_be_bytes());
|
||||
a.push(0xFF);
|
||||
|
||||
let d: &[u32] = &[dim.width, dim.height];
|
||||
let b = (mxc, d, Interfix);
|
||||
let b = serialize_to_vec(b).expect("failed to serialize complex");
|
||||
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_json() {
|
||||
use conduwuit::ruma::api::client::filter::FilterDefinition;
|
||||
|
||||
let filter = FilterDefinition {
|
||||
event_fields: Some(vec!["content.body".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut filter = FilterDefinition::default();
|
||||
filter.event_fields = Some(vec!["content.body".to_owned()]);
|
||||
|
||||
let serialized = serialize_to_vec(Json(&filter)).expect("failed to serialize value");
|
||||
|
||||
@@ -127,10 +91,8 @@ fn ser_json() {
|
||||
fn ser_json_value() {
|
||||
use conduwuit::ruma::api::client::filter::FilterDefinition;
|
||||
|
||||
let filter = FilterDefinition {
|
||||
event_fields: Some(vec!["content.body".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut filter = FilterDefinition::default();
|
||||
filter.event_fields = Some(vec!["content.body".to_owned()]);
|
||||
|
||||
let value = serde_json::to_value(filter).expect("failed to serialize to serde_json::value");
|
||||
let serialized = serialize_to_vec(Json(value)).expect("failed to serialize value");
|
||||
@@ -166,10 +128,8 @@ struct Foo {
|
||||
fn ser_json_raw() {
|
||||
use conduwuit::ruma::api::client::filter::FilterDefinition;
|
||||
|
||||
let filter = FilterDefinition {
|
||||
event_fields: Some(vec!["content.body".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut filter = FilterDefinition::default();
|
||||
filter.event_fields = Some(vec!["content.body".to_owned()]);
|
||||
|
||||
let value =
|
||||
serde_json::value::to_raw_value(&filter).expect("failed to serialize to raw value");
|
||||
@@ -183,10 +143,8 @@ fn ser_json_raw() {
|
||||
fn ser_json_raw_json() {
|
||||
use conduwuit::ruma::api::client::filter::FilterDefinition;
|
||||
|
||||
let filter = FilterDefinition {
|
||||
event_fields: Some(vec!["content.body".to_owned()]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut filter = FilterDefinition::default();
|
||||
filter.event_fields = Some(vec!["content.body".to_owned()]);
|
||||
|
||||
let value =
|
||||
serde_json::value::to_raw_value(&filter).expect("failed to serialize to raw value");
|
||||
@@ -197,11 +155,11 @@ fn ser_json_raw_json() {
|
||||
|
||||
#[test]
|
||||
fn de_tuple() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com\xFF!room:example.com";
|
||||
let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
assert_eq!(b, room_id, "deserialized room_id does not match");
|
||||
@@ -210,11 +168,11 @@ fn de_tuple() {
|
||||
#[test]
|
||||
#[should_panic(expected = "failed to deserialize")]
|
||||
fn de_tuple_invalid() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com\xFF@user:example.com";
|
||||
let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
assert_eq!(b, room_id, "deserialized room_id does not match");
|
||||
@@ -223,10 +181,10 @@ fn de_tuple_invalid() {
|
||||
#[test]
|
||||
#[should_panic(expected = "failed to deserialize")]
|
||||
fn de_tuple_incomplete() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com";
|
||||
let (a, _): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
let (a, _): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
}
|
||||
@@ -234,10 +192,10 @@ fn de_tuple_incomplete() {
|
||||
#[test]
|
||||
#[should_panic(expected = "failed to deserialize")]
|
||||
fn de_tuple_incomplete_with_sep() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com\xFF";
|
||||
let (a, _): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
let (a, _): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
}
|
||||
@@ -248,11 +206,11 @@ fn de_tuple_incomplete_with_sep() {
|
||||
should_panic(expected = "deserialization failed to consume trailing bytes")
|
||||
)]
|
||||
fn de_tuple_unfinished() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com\xFF!room:example.com\xFF@user:example.com";
|
||||
let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
assert_eq!(b, room_id, "deserialized room_id does not match");
|
||||
@@ -260,11 +218,11 @@ fn de_tuple_unfinished() {
|
||||
|
||||
#[test]
|
||||
fn de_tuple_ignore() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let raw: &[u8] = b"@user:example.com\xFF@user2:example.net\xFF!room:example.com";
|
||||
let (a, _, c): (&UserId, Ignore, &RoomId) =
|
||||
let (a, _, c): (OwnedUserId, Ignore, OwnedRoomId) =
|
||||
de::from_slice(raw).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(a, user_id, "deserialized user_id does not match");
|
||||
@@ -360,10 +318,10 @@ fn de_array() {
|
||||
#[test]
|
||||
#[ignore = "Nested sequences are not supported"]
|
||||
fn de_complex() {
|
||||
type Key<'a> = (&'a UserId, ArrayVec<u64, 2>, &'a RoomId);
|
||||
type Key = (OwnedUserId, ArrayVec<u64, 2>, OwnedRoomId);
|
||||
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let a: u64 = 123_456;
|
||||
let b: u64 = 987_654;
|
||||
|
||||
@@ -376,36 +334,36 @@ fn de_complex() {
|
||||
v.extend_from_slice(room_id.as_bytes());
|
||||
|
||||
let arr: &[u64] = &[a, b];
|
||||
let key = (user_id, arr, room_id);
|
||||
let key = (user_id.to_owned(), arr, room_id.to_owned());
|
||||
let s = serialize_to_vec(&key).expect("failed to serialize");
|
||||
|
||||
assert_eq!(&s, &v, "serialization does not match");
|
||||
|
||||
let key = (user_id, [a, b].into(), room_id);
|
||||
let arr: Key<'_> = de::from_slice(&v).expect("failed to deserialize");
|
||||
let key = (user_id.to_owned(), [a, b].into(), room_id.to_owned());
|
||||
let arr: Key = de::from_slice(&v).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(arr, key, "deserialization does not match");
|
||||
|
||||
let arr: Key<'_> = de::from_slice(&s).expect("failed to deserialize");
|
||||
let arr: Key = de::from_slice(&s).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(arr, key, "deserialization of serialization does not match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_value_some() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.extend_from_slice(room_id.as_bytes());
|
||||
aa.push(0xFF);
|
||||
aa.extend_from_slice(user_id.as_bytes());
|
||||
|
||||
let bb: (&RoomId, Option<&UserId>) = (room_id, Some(user_id));
|
||||
let bb: (OwnedRoomId, Option<OwnedUserId>) = (room_id.to_owned(), Some(user_id.to_owned()));
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (&RoomId, Option<&UserId>) =
|
||||
let cc: (OwnedRoomId, Option<OwnedUserId>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(bb.1, cc.1);
|
||||
@@ -414,17 +372,17 @@ fn serde_tuple_option_value_some() {
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_value_none() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.extend_from_slice(room_id.as_bytes());
|
||||
aa.push(0xFF);
|
||||
|
||||
let bb: (&RoomId, Option<&UserId>) = (room_id, None);
|
||||
let bb: (OwnedRoomId, Option<OwnedUserId>) = (room_id.to_owned(), None);
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (&RoomId, Option<&UserId>) =
|
||||
let cc: (OwnedRoomId, Option<OwnedUserId>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(None, cc.1);
|
||||
@@ -433,17 +391,17 @@ fn serde_tuple_option_value_none() {
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_none_value() {
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.push(0xFF);
|
||||
aa.extend_from_slice(user_id.as_bytes());
|
||||
|
||||
let bb: (Option<&RoomId>, &UserId) = (None, user_id);
|
||||
let bb: (Option<OwnedRoomId>, OwnedUserId) = (None, user_id.to_owned());
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, &UserId) =
|
||||
let cc: (Option<OwnedRoomId>, OwnedUserId) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(None, cc.0);
|
||||
@@ -452,19 +410,19 @@ fn serde_tuple_option_none_value() {
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_some_value() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.extend_from_slice(room_id.as_bytes());
|
||||
aa.push(0xFF);
|
||||
aa.extend_from_slice(user_id.as_bytes());
|
||||
|
||||
let bb: (Option<&RoomId>, &UserId) = (Some(room_id), user_id);
|
||||
let bb: (Option<OwnedRoomId>, OwnedUserId) = (Some(room_id.to_owned()), user_id.to_owned());
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, &UserId) =
|
||||
let cc: (Option<OwnedRoomId>, OwnedUserId) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(bb.0, cc.0);
|
||||
@@ -473,19 +431,19 @@ fn serde_tuple_option_some_value() {
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_some_some() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.extend_from_slice(room_id.as_bytes());
|
||||
aa.push(0xFF);
|
||||
aa.extend_from_slice(user_id.as_bytes());
|
||||
|
||||
let bb: (Option<&RoomId>, Option<&UserId>) = (Some(room_id), Some(user_id));
|
||||
let bb: (Option<OwnedRoomId>, Option<OwnedUserId>) = (Some(room_id.to_owned()), Some(user_id.to_owned()));
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, Option<&UserId>) =
|
||||
let cc: (Option<OwnedRoomId>, Option<OwnedUserId>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(cc.0, bb.0);
|
||||
@@ -496,11 +454,11 @@ fn serde_tuple_option_some_some() {
|
||||
fn serde_tuple_option_none_none() {
|
||||
let aa = vec![0xFF];
|
||||
|
||||
let bb: (Option<&RoomId>, Option<&UserId>) = (None, None);
|
||||
let bb: (Option<OwnedRoomId>, Option<OwnedUserId>) = (None, None);
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, Option<&UserId>) =
|
||||
let cc: (Option<OwnedRoomId>, Option<OwnedUserId>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(cc.0, bb.0);
|
||||
@@ -509,8 +467,8 @@ fn serde_tuple_option_none_none() {
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_some_none_some() {
|
||||
let room_id: &RoomId = "!room:example.com".try_into().unwrap();
|
||||
let user_id: &UserId = "@user:example.com".try_into().unwrap();
|
||||
let room_id = room_id!("!room:example.com");
|
||||
let user_id = user_id!("@user:example.com");
|
||||
|
||||
let mut aa = Vec::<u8>::new();
|
||||
aa.extend_from_slice(room_id.as_bytes());
|
||||
@@ -524,24 +482,24 @@ fn serde_tuple_option_some_none_some() {
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) =
|
||||
let cc: (Option<Cow<'_, RoomId>>, Option<Cow<'_, EventId>>, Option<Cow<'_, UserId>>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(bb.0, cc.0);
|
||||
assert_eq!(None, cc.1);
|
||||
assert_eq!(bb.1, cc.1);
|
||||
assert_eq!(bb.2, cc.2);
|
||||
assert_eq!(bb.0, cc.0.as_deref());
|
||||
assert_eq!(None, cc.1.as_deref());
|
||||
assert_eq!(bb.1, cc.1.as_deref());
|
||||
assert_eq!(bb.2, cc.2.as_deref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_tuple_option_none_none_none() {
|
||||
let aa = vec![0xFF, 0xFF];
|
||||
|
||||
let bb: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) = (None, None, None);
|
||||
let bb: (Option<OwnedRoomId>, Option<OwnedEventId>, Option<OwnedUserId>) = (None, None, None);
|
||||
let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple");
|
||||
assert_eq!(aa, bbs);
|
||||
|
||||
let cc: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) =
|
||||
let cc: (Option<OwnedRoomId>, Option<OwnedEventId>, Option<OwnedUserId>) =
|
||||
de::from_slice(&bbs).expect("failed to deserialize tuple");
|
||||
|
||||
assert_eq!(None, cc.0);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "ruminuwuity"
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
|
||||
[features]
|
||||
default = ["client", "server"]
|
||||
client = []
|
||||
server = []
|
||||
|
||||
unstable-exhaustive-types = []
|
||||
unstable-msc3202 = []
|
||||
unstable-msc4203 = []
|
||||
|
||||
[dependencies]
|
||||
ruma.workspace = true
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
wildmatch = "2.6.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1 @@
|
||||
pub mod rooms;
|
||||
@@ -0,0 +1,50 @@
|
||||
pub mod v1 {
|
||||
use ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata};
|
||||
|
||||
metadata! {
|
||||
method: PUT,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
history: {
|
||||
1.0 => "/_continuwuity/admin/rooms/{room_id}/ban",
|
||||
}
|
||||
}
|
||||
|
||||
#[request]
|
||||
pub struct Request {
|
||||
#[ruma_api(path)]
|
||||
pub room_id: OwnedRoomId,
|
||||
|
||||
/// Whether to ban (true) or unban (false) the room.
|
||||
/// If true, and the room is not banned, all local users will be evacuated
|
||||
/// and prevented from re-joining.
|
||||
/// If false, and the room is unbanned, local users will be allowed to re-join.
|
||||
/// No-ops are no-ops.
|
||||
pub banned: bool,
|
||||
}
|
||||
|
||||
#[response]
|
||||
pub struct Response {
|
||||
pub kicked_users: Vec<OwnedUserId>,
|
||||
pub failed_kicked_users: Vec<OwnedUserId>,
|
||||
pub local_aliases: Vec<OwnedRoomAliasId>
|
||||
}
|
||||
|
||||
impl Request {
|
||||
#[must_use]
|
||||
pub fn new(room_id: OwnedRoomId, banned: bool) -> Self {
|
||||
Self { room_id, banned }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
kicked_users: Vec<OwnedUserId>,
|
||||
failed_kicked_users: Vec<OwnedUserId>,
|
||||
local_aliases: Vec<OwnedRoomAliasId>,
|
||||
) -> Self {
|
||||
Self { kicked_users, failed_kicked_users, local_aliases }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, api::{auth_scheme::AccessToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: GET,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
history: {
|
||||
1.0 => "/_continuwuity/admin/rooms/list",
|
||||
}
|
||||
}
|
||||
|
||||
#[request]
|
||||
#[derive(Default)]
|
||||
pub struct Request;
|
||||
|
||||
#[response]
|
||||
pub struct Response {
|
||||
pub rooms: Vec<OwnedRoomId>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
#[must_use]
|
||||
pub fn new(rooms: Vec<OwnedRoomId>) -> Self {
|
||||
Self { rooms }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod list;
|
||||
pub mod ban;
|
||||
@@ -0,0 +1,53 @@
|
||||
//! `GET /_matrix/client/v1/admin/suspend/{userId}`
|
||||
//!
|
||||
//! Check the suspension status of a target user
|
||||
|
||||
pub mod v1 {
|
||||
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc])
|
||||
//!
|
||||
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
|
||||
|
||||
use ruma::{
|
||||
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: GET,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
history: {
|
||||
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the get & set user suspension status endpoint.
|
||||
#[request(error = ruma::api::client::Error)]
|
||||
pub struct Request {
|
||||
/// The user to look up.
|
||||
#[ruma_api(path)]
|
||||
pub user_id: OwnedUserId,
|
||||
}
|
||||
|
||||
/// Response type for the suspension endpoints
|
||||
#[response(error = ruma::api::client::Error)]
|
||||
pub struct Response {
|
||||
/// Whether the user is currently suspended.
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Creates a new `Request` with the given user id.
|
||||
#[must_use]
|
||||
pub fn new(user_id: OwnedUserId) -> Self {
|
||||
Self { user_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given suspension status.
|
||||
#[must_use]
|
||||
pub fn new(suspended: bool) -> Self {
|
||||
Self { suspended }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod continuwuity;
|
||||
pub mod get_suspended;
|
||||
pub mod set_suspended;
|
||||
@@ -0,0 +1,55 @@
|
||||
//! `PUT /_matrix/client/v1/admin/suspend/{userId}`
|
||||
//!
|
||||
//! Set the suspension status of a target user
|
||||
|
||||
pub mod v1 {
|
||||
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc])
|
||||
//!
|
||||
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
|
||||
|
||||
use ruma::{
|
||||
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: PUT,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
history: {
|
||||
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the set user suspension status endpoint.
|
||||
#[request(error = ruma::api::client::Error)]
|
||||
pub struct Request {
|
||||
/// The user to look up.
|
||||
#[ruma_api(path)]
|
||||
pub user_id: OwnedUserId,
|
||||
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
/// Response type for the suspension endpoints
|
||||
#[response(error = ruma::api::client::Error)]
|
||||
pub struct Response {
|
||||
/// Whether the user is currently suspended.
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Creates a new `Request` with the given user id.
|
||||
#[must_use]
|
||||
pub fn new(user_id: OwnedUserId, suspended: bool) -> Self {
|
||||
Self { user_id, suspended }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given suspension status.
|
||||
#[must_use]
|
||||
pub fn new(suspended: bool) -> Self {
|
||||
Self { suspended }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod user_may_invite;
|
||||
pub mod user_may_join_room;
|
||||
@@ -0,0 +1,50 @@
|
||||
//! `POST /api/1/spam_check/user_may_invite`
|
||||
//!
|
||||
//! Checks that a user may invite the given user to the given room via Draupnir anti-spam
|
||||
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AppserviceToken,
|
||||
history: {
|
||||
1.0 => "/api/1/spam_check/user_may_invite",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `user_may_invite` callback.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The room the invitee is being invited to
|
||||
pub room_id: OwnedRoomId,
|
||||
/// The user sending the invite
|
||||
pub inviter: OwnedUserId,
|
||||
/// The user being invited
|
||||
pub invitee: OwnedUserId,
|
||||
}
|
||||
|
||||
/// Response type for the `user_may_invite` callback.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a new empty `Request`.
|
||||
#[must_use]
|
||||
pub fn new(room_id: OwnedRoomId, inviter: OwnedUserId, invitee: OwnedUserId) -> Self {
|
||||
Self { room_id, inviter, invitee }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//! `POST /api/1/spam_check/user_may_join_room`
|
||||
//!
|
||||
//! Endpoint that checks whether a user may join a given room via Draupnir anti-spam
|
||||
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AppserviceToken,
|
||||
history: {
|
||||
1.0 => "/api/1/spam_check/user_may_join_room",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `user_may_join_room` callback.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The user trying to join a room
|
||||
pub user: OwnedUserId,
|
||||
/// The room the user is trying to join
|
||||
pub room: OwnedRoomId,
|
||||
/// Whether the user was invited to this room
|
||||
pub is_invited: bool,
|
||||
}
|
||||
|
||||
/// Response type for the `user_may_join_room` callback.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a new empty `Request`.
|
||||
#[must_use]
|
||||
pub fn new(user: OwnedUserId, room: OwnedRoomId, is_invited: bool) -> Self {
|
||||
Self { user, room, is_invited }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
//! Types for invite filtering ([MSC4155]).
|
||||
//!
|
||||
//! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155
|
||||
|
||||
use ruma::{ServerName, UserId};
|
||||
use ruma::exports::ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
/// Represents a user's level of filtering on actions from another user or server.
|
||||
/// "Ignore" and "block" are defined in [MSC4283].
|
||||
///
|
||||
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FilterLevel {
|
||||
Allow,
|
||||
Ignore,
|
||||
Block,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.invite_permission_config", kind = GlobalAccountData)]
|
||||
pub struct InvitePermissionConfigEventContent {
|
||||
/// A global on/off toggle for all rules
|
||||
#[serde(default = "ruma::serde::default_true")]
|
||||
pub enabled: bool,
|
||||
|
||||
/// A list of globs matching users which are allowed to send an invite.
|
||||
/// Entries in this list supersede entries in the ignored and blocked lists.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub allowed_users: Vec<String>,
|
||||
/// A list of globs matching users whose invites should be ignored (as defined in [MSC4283]).
|
||||
///
|
||||
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub ignored_users: Vec<String>,
|
||||
/// A list of globs matching users whose invites should be blocked (as defined in [MSC4283]).
|
||||
/// Invites from blocked users should be refused with the M_INVITE_BLOCKED status code.
|
||||
///
|
||||
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub blocked_users: Vec<String>,
|
||||
|
||||
|
||||
/// A list of globs matching servers which are allowed to send an invite.
|
||||
/// Entries in this list supersede entries in the ignored and blocked lists.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub allowed_servers: Vec<String>,
|
||||
/// A list of globs matching servers whose invites should be ignored (as defined in [MSC4283]).
|
||||
///
|
||||
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub ignored_servers: Vec<String>,
|
||||
/// A list of globs matching servers whose invites should be blocked (as defined in [MSC4283]).
|
||||
/// Invites from blocked servers should be refused with the M_INVITE_BLOCKED status code.
|
||||
///
|
||||
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub blocked_servers: Vec<String>,
|
||||
}
|
||||
|
||||
impl InvitePermissionConfigEventContent {
|
||||
/// Creates a new `InvitePermissionConfigEventContent` from six lists of globs.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
enabled: bool,
|
||||
allowed_users: Vec<String>,
|
||||
ignored_users: Vec<String>,
|
||||
blocked_users: Vec<String>,
|
||||
allowed_servers: Vec<String>,
|
||||
ignored_servers: Vec<String>,
|
||||
blocked_servers: Vec<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
allowed_users,
|
||||
ignored_users,
|
||||
blocked_users,
|
||||
allowed_servers,
|
||||
ignored_servers,
|
||||
blocked_servers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the filters against a user id. This function will check both the
|
||||
/// user rules _and_ the server rules.
|
||||
#[must_use]
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
pub fn user_filter_level(&self, user: &UserId) -> FilterLevel {
|
||||
if !self.enabled {
|
||||
FilterLevel::Allow
|
||||
} else if Self::matches(&self.allowed_users, user.as_str()) {
|
||||
FilterLevel::Allow
|
||||
} else if Self::matches(&self.ignored_users, user.as_str()) {
|
||||
FilterLevel::Ignore
|
||||
} else if Self::matches(&self.blocked_users, user.as_str()) {
|
||||
FilterLevel::Block
|
||||
} else {
|
||||
self.server_filter_level(user.server_name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the filters against a server name. Port numbers are ignored.
|
||||
#[must_use]
|
||||
pub fn server_filter_level(&self, server: &ServerName) -> FilterLevel {
|
||||
if !self.enabled {
|
||||
FilterLevel::Allow
|
||||
} else {
|
||||
let server = server.host();
|
||||
if Self::matches(&self.allowed_servers, server) {
|
||||
FilterLevel::Allow
|
||||
} else if Self::matches(&self.ignored_servers, server) {
|
||||
FilterLevel::Ignore
|
||||
} else if Self::matches(&self.blocked_servers, server) {
|
||||
FilterLevel::Block
|
||||
} else {
|
||||
FilterLevel::Allow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(a: &[String], s: &str) -> bool {
|
||||
a.iter().map(String::as_str).any(|a| WildMatch::new(a).matches(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::{ServerName, UserId, events::GlobalAccountDataEvent};
|
||||
use serde_json::{from_value as from_json_value, json};
|
||||
|
||||
use crate::invite_permission_config::{FilterLevel, InvitePermissionConfigEventContent};
|
||||
|
||||
fn user_id(id: &str) -> &UserId {
|
||||
<&UserId>::try_from(id).unwrap()
|
||||
}
|
||||
|
||||
fn server_name(name: &str) -> &ServerName {
|
||||
<&ServerName>::try_from(name).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_values() {
|
||||
let data = json!({
|
||||
"content": {},
|
||||
"type": "org.matrix.msc4155.invite_permission_config"
|
||||
});
|
||||
|
||||
let event: GlobalAccountDataEvent<InvitePermissionConfigEventContent> = from_json_value(data).unwrap();
|
||||
assert!(event.content.enabled);
|
||||
assert!(event.content.allowed_users.is_empty());
|
||||
assert!(event.content.ignored_users.is_empty());
|
||||
assert!(event.content.blocked_users.is_empty());
|
||||
assert!(event.content.allowed_servers.is_empty());
|
||||
assert!(event.content.ignored_servers.is_empty());
|
||||
assert!(event.content.blocked_servers.is_empty());
|
||||
assert_eq!(event.content.user_filter_level(user_id("@alice:example.com")), FilterLevel::Allow);
|
||||
assert_eq!(event.content.server_filter_level(server_name("example.com")), FilterLevel::Allow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_the_world() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
blocked_servers: vec!["*".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:foo.com:8080")), FilterLevel::Block);
|
||||
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_goodguys() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
allowed_servers: vec!["goodguys.org".to_owned()],
|
||||
blocked_servers: vec!["*".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_badguys() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
blocked_servers: vec!["badguys.org".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org:8080")), FilterLevel::Block);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_goodguys_except_for_kevin() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
blocked_users: vec!["@kevin:goodguys.org".to_owned()],
|
||||
allowed_servers: vec!["goodguys.org".to_owned()],
|
||||
blocked_servers: vec!["*".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:goodguys.org")), FilterLevel::Block);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_badguys_except_for_alice() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
allowed_users: vec!["@alice:badguys.org".to_owned()],
|
||||
blocked_servers: vec!["badguys.org".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:badguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_goodguys_and_ignore_reallybadguys() {
|
||||
let event = InvitePermissionConfigEventContent {
|
||||
enabled: true,
|
||||
allowed_servers: vec!["goodguys.org".to_owned()],
|
||||
ignored_servers: vec!["reallybadguys.org".to_owned()],
|
||||
blocked_servers: vec!["*".to_owned()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
|
||||
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
|
||||
assert_eq!(event.user_filter_level(user_id("@kevin:reallybadguys.org")), FilterLevel::Ignore);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//! `POST /_meowlnir/antispam/*/accept_make_join`
|
||||
//!
|
||||
//! Endpoint to accept or decline incoming make_join federation requests.
|
||||
//! Used by the `fi.mau.spam_check` restricted join rule.
|
||||
//!
|
||||
//! References:
|
||||
//! - https://mau.dev/maunium/synapse/-/blob/52741d3/synapse/handlers/event_auth.py#L280-292
|
||||
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AppserviceToken,
|
||||
history: {
|
||||
1.0 => "/_meowlnir/antispam/{management_room_id}/accept_make_join",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `accept_make_join` callback.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The relevant management room
|
||||
#[ruma_api(path)]
|
||||
pub management_room_id: OwnedRoomId,
|
||||
/// The user trying to join a room
|
||||
pub user: OwnedUserId,
|
||||
/// The room the user is trying to join
|
||||
pub room: OwnedRoomId,
|
||||
}
|
||||
|
||||
/// Response type for the `accept_make_join` callback.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a new empty `Request`.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
management_room_id: OwnedRoomId,
|
||||
user: OwnedUserId,
|
||||
room: OwnedRoomId,
|
||||
) -> Self {
|
||||
Self { management_room_id, user, room }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod user_may_invite;
|
||||
pub mod user_may_join_room;
|
||||
|
||||
pub mod accept_make_join;
|
||||
@@ -0,0 +1,58 @@
|
||||
//! `POST /_meowlnir/antispam/*/user_may_invite`
|
||||
//!
|
||||
//! Checks that a user may invite the given user to the given room via Meowlnir anti-spam
|
||||
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AppserviceToken,
|
||||
history: {
|
||||
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_invite",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `user_may_invite` callback.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The relevant management room
|
||||
#[ruma_api(path)]
|
||||
pub management_room_id: OwnedRoomId,
|
||||
/// The user sending the invite
|
||||
pub inviter: OwnedUserId,
|
||||
/// The user being invited
|
||||
pub invitee: OwnedUserId,
|
||||
/// The room the invitee is being invited to
|
||||
pub room_id: OwnedRoomId,
|
||||
}
|
||||
|
||||
/// Response type for the `user_may_invite` callback.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a new empty `Request`.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
management_room_id: OwnedRoomId,
|
||||
inviter: OwnedUserId,
|
||||
invitee: OwnedUserId,
|
||||
room_id: OwnedRoomId,
|
||||
) -> Self {
|
||||
Self { management_room_id, inviter, invitee, room_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//! `POST /_meowlnir/antispam/*/user_may_join_room`
|
||||
//!
|
||||
//! Endpoint to track invite joins via Meowlnir anti-spam
|
||||
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AppserviceToken,
|
||||
history: {
|
||||
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_join_room",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `user_may_join_room` callback.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The relevant management room
|
||||
#[ruma_api(path)]
|
||||
pub management_room_id: OwnedRoomId,
|
||||
/// The user trying to join a room
|
||||
pub user: OwnedUserId,
|
||||
/// The room the user is trying to join
|
||||
pub room: OwnedRoomId,
|
||||
/// Whether the user was invited to this room
|
||||
pub is_invited: bool,
|
||||
}
|
||||
|
||||
/// Response type for the `user_may_join_room` callback.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a new empty `Request`.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
management_room_id: OwnedRoomId,
|
||||
user: OwnedUserId,
|
||||
room: OwnedRoomId,
|
||||
is_invited: bool,
|
||||
) -> Self {
|
||||
Self { management_room_id, user, room, is_invited }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Ruminuwuity: Continuwuity-specific APIs and structs that depend only on Ruma
|
||||
|
||||
pub mod admin;
|
||||
pub mod draupnir_antispam;
|
||||
pub mod meowlnir_antispam;
|
||||
pub mod policy;
|
||||
pub mod invite_permission_config;
|
||||
@@ -0,0 +1,100 @@
|
||||
//! Types for the [`org.matrix.msc4284.policy`] event.
|
||||
//!
|
||||
//! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
|
||||
|
||||
use ruma::exports::ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruma::events::EmptyStateKey;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent, Default)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "org.matrix.msc4284.policy", kind = State, state_key_type = EmptyStateKey)]
|
||||
pub struct RoomPolicyEventContent {
|
||||
/// The server name of the room's policy server.
|
||||
///
|
||||
/// If the value is empty or unreachable, the policy server should be ignored.
|
||||
pub via: Option<String>,
|
||||
/// The public key this policy server will sign with.
|
||||
pub public_key: Option<String>
|
||||
}
|
||||
|
||||
impl RoomPolicyEventContent {
|
||||
/// Create an empty `RoomPolicyEventContent`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct PolicyServerResponseContent {
|
||||
/// The policy server's verdict. Either `ok` or `spam`.
|
||||
pub recommendation: String,
|
||||
}
|
||||
|
||||
impl PolicyServerResponseContent {
|
||||
/// Create a new `PolicyServerResponseContent` with the given recommendation.
|
||||
#[must_use]
|
||||
pub fn new(recommendation: String) -> Self {
|
||||
Self { recommendation }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for PolicyServerResponseContent {
|
||||
fn from(recommendation: String) -> Self {
|
||||
Self::new(recommendation)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
use super::RoomPolicyEventContent;
|
||||
use ruma::events::OriginalStateEvent;
|
||||
|
||||
#[test]
|
||||
fn serialization() {
|
||||
let content = RoomPolicyEventContent {
|
||||
via: Some("example.com".to_owned()),
|
||||
public_key: Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
|
||||
};
|
||||
|
||||
let actual = to_json_value(content).unwrap();
|
||||
let expected = json!({
|
||||
"via": "example.com",
|
||||
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
|
||||
});
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization() {
|
||||
let json_data = json!({
|
||||
"content": {
|
||||
"via": "example.com",
|
||||
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
|
||||
},
|
||||
"event_id": "123:example.com",
|
||||
"origin_server_ts": 1,
|
||||
"room_id": "!123456:example.com",
|
||||
"sender": "@carl:example.com",
|
||||
"state_key": "",
|
||||
"type": "org.matrix.msc4284.policy"
|
||||
});
|
||||
|
||||
let content = from_json_value::<OriginalStateEvent<RoomPolicyEventContent>>(json_data)
|
||||
.unwrap()
|
||||
.content;
|
||||
assert_eq!(
|
||||
content.via,
|
||||
Some("example.com".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
content.public_key,
|
||||
Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod policy_check;
|
||||
pub mod policy_sign;
|
||||
pub mod report_content;
|
||||
pub mod event;
|
||||
@@ -0,0 +1,60 @@
|
||||
//! `POST /_matrix/policy/unstable/org.matrix.msc4284/event/{eventId}/check`
|
||||
//!
|
||||
//! Checks if an event is allowed by the room's policy server.
|
||||
//! This is now a fallback behaviour that will be removed later.
|
||||
|
||||
pub mod unstable {
|
||||
//! `/policy/unstable/org.matrix.msc4284` ([spec])
|
||||
//!
|
||||
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
|
||||
|
||||
use ruma::{
|
||||
OwnedEventId, api::{federation::authentication::ServerSignatures, request, response}, metadata
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: ServerSignatures,
|
||||
history: {
|
||||
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/event/{event_id}/check",
|
||||
}
|
||||
}
|
||||
|
||||
/// Response type for the `check` endpoint.
|
||||
#[response]
|
||||
pub struct Response {
|
||||
/// Either `ok` or `spam`, indicating the policy server's recommendation.
|
||||
pub recommendation: String,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given recommendation.
|
||||
#[must_use]
|
||||
pub fn new(recommendation: String) -> Self {
|
||||
Self { recommendation }
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `check` endpoint.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The event ID to check.
|
||||
#[ruma_api(path)]
|
||||
pub event_id: OwnedEventId,
|
||||
|
||||
/// The PDU body (optional)
|
||||
#[ruma_api(body)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pdu: Option<Box<RawJsonValue>>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Creates a new `Request` with the given event ID.
|
||||
#[must_use]
|
||||
pub fn new(event_id: OwnedEventId) -> Self {
|
||||
Self { event_id, pdu: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//! `POST /_matrix/policy/unstable/org.matrix.msc4284/sign`
|
||||
//!
|
||||
//! Asks a policy server to sign our event
|
||||
|
||||
pub mod unstable {
|
||||
//! `/policy/unstable/org.matrix.msc4284` ([spec])
|
||||
//!
|
||||
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
|
||||
use ruma::{
|
||||
ServerSignatures,
|
||||
api::{federation::authentication::ServerSignatures as ServerSignaturesAuth, request, response}, metadata
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: ServerSignaturesAuth,
|
||||
history: {
|
||||
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/sign",
|
||||
}
|
||||
}
|
||||
|
||||
/// Response type for the `sign` endpoint.
|
||||
#[response]
|
||||
pub struct Response {
|
||||
/// The signatures returned from the policy server (if provided)
|
||||
#[ruma_api(body)]
|
||||
pub signatures: Option<ServerSignatures>
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given recommendation.
|
||||
#[must_use]
|
||||
pub fn new(signatures: Option<ServerSignatures>) -> Self {
|
||||
Self { signatures }
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `sign` endpoint.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The PDU body (in canonical JSON)
|
||||
#[ruma_api(body)]
|
||||
pub pdu: Box<RawJsonValue>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Creates a new `Request` with the given event JSON
|
||||
#[must_use]
|
||||
pub fn new(pdu: Box<RawJsonValue>) -> Self {
|
||||
Self { pdu }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//! `GET /_matrix/federation/*/rooms/{roomId}/report/{eventId}`
|
||||
//!
|
||||
//! Send a request to report an event originating from another server.
|
||||
|
||||
pub mod msc3843 {
|
||||
//! `MSC3843` ([MSC])
|
||||
//!
|
||||
//! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3843
|
||||
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId, api::{federation::authentication::ServerSignatures, request, response}, metadata
|
||||
};
|
||||
|
||||
metadata! {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: ServerSignatures,
|
||||
history: {
|
||||
unstable => "/_matrix/federation/unstable/org.matrix.msc3843/rooms/{room_id}/report/{event_id}",
|
||||
}
|
||||
}
|
||||
|
||||
/// Request type for the `report_content` endpoint.
|
||||
#[request]
|
||||
pub struct Request {
|
||||
/// The room ID that the reported event was sent in.
|
||||
#[ruma_api(path)]
|
||||
pub room_id: OwnedRoomId,
|
||||
|
||||
/// The event being reported.
|
||||
#[ruma_api(path)]
|
||||
pub event_id: OwnedEventId,
|
||||
|
||||
/// The reason that the event is being reported.
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// Response type for the `report_content` endpoint.
|
||||
#[response]
|
||||
#[derive(Default)]
|
||||
pub struct Response;
|
||||
|
||||
impl Request {
|
||||
/// Creates a `Request` with the given room ID, event ID and reason.
|
||||
#[must_use]
|
||||
pub fn new(room_id: OwnedRoomId, event_id: OwnedEventId, reason: String) -> Self {
|
||||
Self { room_id, event_id, reason }
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new empty `Response`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ rand.workspace = true
|
||||
regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
ruma.workspace = true
|
||||
ruminuwuity.workspace = true
|
||||
rustyline-async.workspace = true
|
||||
rustyline-async.optional = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -7,17 +7,21 @@
|
||||
use database::{Deserialized, Handle, Ignore, Json, Map};
|
||||
use futures::{Stream, StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
RoomId, UserId,
|
||||
events::{
|
||||
AnyGlobalAccountDataEvent, AnyRawAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId, events::{
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
}, serde::Raw
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{Dep, globals};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnyRawAccountDataEvent {
|
||||
Room(Raw<AnyRoomAccountDataEvent>),
|
||||
Global(Raw<AnyGlobalAccountDataEvent>),
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
db: Data,
|
||||
@@ -132,7 +136,7 @@ pub fn changes_since<'a>(
|
||||
since: Option<u64>,
|
||||
to: Option<u64>,
|
||||
) -> impl Stream<Item = AnyRawAccountDataEvent> + Send + 'a {
|
||||
type Key<'a> = (Option<&'a RoomId>, &'a UserId, u64, Ignore);
|
||||
type Key = (Option<OwnedRoomId>, OwnedUserId, u64, Ignore);
|
||||
|
||||
// Skip the data that's exactly at since, because we sent that last time
|
||||
// ...unless this is an initial sync, in which case send everything
|
||||
@@ -142,8 +146,8 @@ pub fn changes_since<'a>(
|
||||
.roomuserdataid_accountdata
|
||||
.stream_from(&first_possible)
|
||||
.ignore_err()
|
||||
.ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key<'_>, _)| {
|
||||
room_id == *room_id_ && user_id == *user_id_ && to.is_none_or(|to| *count <= to)
|
||||
.ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key, _)| {
|
||||
room_id == room_id_.as_deref() && user_id == user_id_ && to.is_none_or(|to| *count <= to)
|
||||
})
|
||||
.map(move |(_, v)| {
|
||||
match room_id {
|
||||
|
||||
+18
-38
@@ -13,7 +13,6 @@
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
preview_url::RoomPreviewUrlsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
};
|
||||
@@ -25,7 +24,7 @@
|
||||
/// Users in this room are considered admins by conduwuit, and the room can be
|
||||
/// used to issue admin commands by talking to the server user inside it.
|
||||
pub async fn create_admin_room(services: &Services) -> Result {
|
||||
let room_id = RoomId::new(services.globals.server_name());
|
||||
let room_id = RoomId::new_v1(services.globals.server_name());
|
||||
let room_version = &RoomVersionId::V11;
|
||||
|
||||
let _short_id = services
|
||||
@@ -34,22 +33,24 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
|
||||
|
||||
// Create a user for the server
|
||||
let server_user = services.globals.server_user.as_ref();
|
||||
services.users.create(server_user, None, None).await?;
|
||||
|
||||
let create_content = {
|
||||
let mut create_content = {
|
||||
use RoomVersionId::*;
|
||||
match room_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
RoomCreateEventContent::new_v1(server_user.into()),
|
||||
| V11 => RoomCreateEventContent::new_v11(),
|
||||
| _ => RoomCreateEventContent::new_v12(),
|
||||
| _ => RoomCreateEventContent::new_v11(),
|
||||
}
|
||||
};
|
||||
|
||||
create_content.federate = true;
|
||||
create_content.room_version = room_version.clone();
|
||||
|
||||
info!("Creating admin room {} with version {}", room_id, room_version);
|
||||
|
||||
// 1. The room create event
|
||||
@@ -57,12 +58,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomCreateEventContent {
|
||||
federate: true,
|
||||
predecessor: None,
|
||||
room_version: room_version.clone(),
|
||||
..create_content
|
||||
}),
|
||||
PduBuilder::state(String::new(), &create_content),
|
||||
server_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
@@ -89,14 +85,14 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
// 3. Power levels
|
||||
let users = BTreeMap::from_iter([(server_user.into(), 69420.into())]);
|
||||
|
||||
let mut power_levels_content = RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization);
|
||||
power_levels_content.users = users;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomPowerLevelsEventContent {
|
||||
users,
|
||||
..Default::default()
|
||||
}),
|
||||
PduBuilder::state(String::new(), &power_levels_content),
|
||||
server_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
@@ -163,13 +159,12 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let room_topic = format!("Manage {} | Run commands prefixed with `!admin` | Run `!admin -h` for help | Documentation: https://continuwuity.org/", services.config.server_name);
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent {
|
||||
topic: format!("Manage {} | Run commands prefixed with `!admin` | Run `!admin -h` for help | Documentation: https://continuwuity.org/", services.config.server_name),
|
||||
}),
|
||||
PduBuilder::state(String::new(), &RoomTopicEventContent::markdown(room_topic)),
|
||||
server_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
@@ -178,16 +173,14 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
.await?;
|
||||
|
||||
// 6. Room alias
|
||||
let alias = &services.globals.admin_alias;
|
||||
let mut alias_content = RoomCanonicalAliasEventContent::new();
|
||||
alias_content.alias = Some(services.globals.admin_alias.clone());
|
||||
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
|
||||
alias: Some(alias.clone()),
|
||||
alt_aliases: Vec::new(),
|
||||
}),
|
||||
PduBuilder::state(String::new(), &alias_content),
|
||||
server_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
@@ -198,20 +191,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(alias, &room_id, server_user)?;
|
||||
|
||||
// 7. (ad-hoc) Disable room URL previews for everyone by default
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &RoomPreviewUrlsEventContent { disabled: true }),
|
||||
server_user,
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
.set_alias(&services.globals.admin_alias, &room_id, server_user)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
let state_lock = self.services.state.mutex.lock(room_id.as_str()).await;
|
||||
|
||||
if self.services.state_cache.is_joined(user_id, &room_id).await {
|
||||
return Err!(debug_warn!("User is already joined in the admin room"));
|
||||
@@ -100,7 +100,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
.expect("admin room should have power levels");
|
||||
|
||||
room_power_levels
|
||||
.users
|
||||
@@ -135,9 +135,7 @@ async fn set_room_tag(&self, room_id: &RoomId, user_id: &UserId, tag: &str) -> R
|
||||
.account_data
|
||||
.get_room(room_id, user_id, RoomAccountDataEventType::Tag)
|
||||
.await
|
||||
.unwrap_or_else(|_| TagEvent {
|
||||
content: TagEventContent { tags: BTreeMap::new() },
|
||||
});
|
||||
.unwrap_or_else(|_| TagEvent::new(TagEventContent::new(BTreeMap::new())));
|
||||
|
||||
event
|
||||
.content
|
||||
@@ -177,9 +175,9 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
|
||||
return Err!(error!("No admin room available or created."));
|
||||
};
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
let state_lock = self.services.state.mutex.lock(room_id.as_str()).await;
|
||||
|
||||
let event = match self
|
||||
let mut member_content = match self
|
||||
.services
|
||||
.state_accessor
|
||||
.get_member(&room_id, user_id)
|
||||
@@ -203,17 +201,13 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
|
||||
},
|
||||
};
|
||||
|
||||
member_content.membership = Leave;
|
||||
member_content.reason = Some("Admin Revoked".to_owned());
|
||||
|
||||
self.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
membership: Leave,
|
||||
reason: Some("Admin Revoked".into()),
|
||||
is_direct: None,
|
||||
join_authorized_via_users_server: None,
|
||||
third_party_invite: None,
|
||||
..event
|
||||
}),
|
||||
PduBuilder::state(user_id.to_string(), &member_content),
|
||||
self.services.globals.server_user.as_ref(),
|
||||
Some(&room_id),
|
||||
&state_lock,
|
||||
|
||||
+11
-15
@@ -17,7 +17,7 @@
|
||||
use futures::{Future, FutureExt, StreamExt, TryFutureExt};
|
||||
use loole::{Receiver, Sender};
|
||||
use ruma::{
|
||||
Mxc, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
|
||||
OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
|
||||
events::{
|
||||
Mentions,
|
||||
room::{
|
||||
@@ -30,7 +30,7 @@
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{Dep, account_data, globals, media::MXC_LENGTH, rooms, rooms::state::RoomMutexGuard};
|
||||
use crate::{Dep, account_data, globals, media::{MXC_LENGTH, mxc::Mxc}, rooms::{self, state::RoomMutexGuard}};
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
@@ -200,19 +200,15 @@ pub async fn text_or_file(
|
||||
.await
|
||||
.expect("failed to create text file");
|
||||
let size_u64: u64 = message_content.body().len().try_into().map_or(0, |n| n);
|
||||
let metadata = FileInfo {
|
||||
mimetype: Some("text/markdown".to_owned()),
|
||||
size: Some(UInt::new_saturating(size_u64)),
|
||||
thumbnail_info: None,
|
||||
thumbnail_source: None,
|
||||
};
|
||||
let content = FileMessageEventContent {
|
||||
body: "Output was too large to send as text.".to_owned(),
|
||||
formatted: None,
|
||||
filename: Some("output.md".to_owned()),
|
||||
source: MediaSource::Plain(file),
|
||||
info: Some(Box::new(metadata)),
|
||||
};
|
||||
|
||||
let mut metadata = FileInfo::new();
|
||||
metadata.mimetype = Some("text/markdown".to_owned());
|
||||
metadata.size = Some(UInt::new_saturating(size_u64));
|
||||
|
||||
let mut content = FileMessageEventContent::plain("Output was too large to send as text.".to_owned(), file);
|
||||
content.filename = Some("output.md".to_owned());
|
||||
content.info = Some(Box::new(metadata));
|
||||
|
||||
RoomMessageEventContent::new(MessageType::File(content))
|
||||
} else {
|
||||
message_content
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Result, config::Antispam, debug};
|
||||
use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam};
|
||||
use ruma::{OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, path_builder::VersionHistory}};
|
||||
use ruminuwuity::{draupnir_antispam, meowlnir_antispam};
|
||||
|
||||
use crate::{client, config, sending, service::Dep};
|
||||
|
||||
@@ -37,7 +38,7 @@ async fn send_antispam_request<T>(
|
||||
request: T,
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: ruma::api::OutgoingRequest + Debug + Send,
|
||||
T: ruma::api::OutgoingRequest<Authentication = AppserviceToken, PathBuilder = VersionHistory> + Debug + Send,
|
||||
{
|
||||
sending::antispam::send_antispam_request(
|
||||
&self.services.client.appservice,
|
||||
|
||||
@@ -72,9 +72,7 @@ async fn set_emergency_access(&self) -> Result {
|
||||
None,
|
||||
server_user,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(&GlobalAccountDataEvent {
|
||||
content: PushRulesEventContent { global: ruleset },
|
||||
})
|
||||
&serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(ruleset)))
|
||||
.expect("to json value always works"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,64 +1,85 @@
|
||||
use std::{fmt::Debug, mem};
|
||||
use std::{any::Any, borrow::Cow, fmt::Debug, mem, sync::LazyLock};
|
||||
|
||||
use bytes::Bytes;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, implement,
|
||||
trace, utils::response::LimitReadExt,
|
||||
};
|
||||
use http::{HeaderValue, header::AUTHORIZATION};
|
||||
Err, Error, Result, debug, debug_error, debug_warn, err, implement, trace, utils::response::LimitReadExt, matrix::versions::{unstable_features, versions}, };
|
||||
use ipaddress::IPAddress;
|
||||
use reqwest::{Client, Method, Request, Response, Url};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId,
|
||||
api::{
|
||||
EndpointError, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||
client::error::Error as RumaError, federation::authentication::XMatrix,
|
||||
},
|
||||
serde::Base64,
|
||||
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId, api::{
|
||||
EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions, auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken}, client::error::Error as RumaError, federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix}, path_builder::{PathBuilder, SinglePath, VersionHistory}
|
||||
}, serde::Base64
|
||||
};
|
||||
|
||||
use crate::resolver::actual::ActualDest;
|
||||
use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest};
|
||||
|
||||
/// Sends a request to a federation server
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(skip_all, name = "request", level = "debug")]
|
||||
pub async fn execute<T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
|
||||
pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
|
||||
{
|
||||
let client = &self.services.client.federation;
|
||||
self.execute_on(client, dest, request).await
|
||||
self.execute_signed(client, dest, request).await
|
||||
}
|
||||
|
||||
/// Like execute() but with a very large timeout
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(skip_all, name = "synapse", level = "debug")]
|
||||
pub async fn execute_synapse<T>(
|
||||
pub async fn execute_synapse<'i, T>(
|
||||
&self,
|
||||
dest: &ServerName,
|
||||
request: T,
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
|
||||
{
|
||||
let client = &self.services.client.synapse;
|
||||
self.execute_on(client, dest, request).await
|
||||
self.execute_signed(client, dest, request).await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
pub async fn execute_unauthenticated<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
|
||||
{
|
||||
let client = &self.services.client.federation;
|
||||
let authentication = SendAccessToken::None;
|
||||
|
||||
self.execute_on(client, dest, request, authentication).await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
pub async fn execute_signed<'i, T>(&self, client: &Client, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Send,
|
||||
{
|
||||
let authentication = ServerSignaturesInput::new(
|
||||
self.services.server.name.clone(),
|
||||
dest.to_owned(),
|
||||
self.services.server_keys.keypair(),
|
||||
);
|
||||
|
||||
self.execute_on(client, dest, request, authentication).await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(
|
||||
name = "fed",
|
||||
level = INFO_SPAN_LEVEL,
|
||||
skip(self, client, request),
|
||||
level = "info",
|
||||
skip(self, client, request, authentication),
|
||||
)]
|
||||
pub async fn execute_on<T>(
|
||||
pub async fn execute_on<'i, T, PathBuilderInput>(
|
||||
&self,
|
||||
client: &Client,
|
||||
dest: &ServerName,
|
||||
request: T,
|
||||
authentication: <T::Authentication as AuthScheme>::Input<'_>,
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Send,
|
||||
T: OutgoingRequest::<PathBuilder: PathBuilder<Input<'i> = PathBuilderInput>> + Send,
|
||||
PathBuilderInput: FederationPathBuilderInput
|
||||
{
|
||||
if !self.services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
@@ -69,8 +90,17 @@ pub async fn execute_on<T>(
|
||||
}
|
||||
|
||||
let actual = self.services.resolver.get_actual_dest(dest).await?;
|
||||
let request = into_http_request::<T>(&actual, request)?;
|
||||
let request = self.prepare(dest, request)?;
|
||||
|
||||
let request = Request::try_from(
|
||||
request.try_into_http_request::<Vec<u8>>(
|
||||
actual.string().as_str(),
|
||||
authentication,
|
||||
PathBuilderInput::create(),
|
||||
)?
|
||||
)?;
|
||||
self.validate_url(request.url())?;
|
||||
self.services.server.check_running()?;
|
||||
|
||||
self.perform::<T>(dest, &actual, request, client).await
|
||||
}
|
||||
|
||||
@@ -98,17 +128,6 @@ async fn perform<T>(
|
||||
}
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
fn prepare(&self, dest: &ServerName, mut request: http::Request<Vec<u8>>) -> Result<Request> {
|
||||
self.sign_request(&mut request, dest);
|
||||
|
||||
let request = Request::try_from(request)?;
|
||||
self.validate_url(request.url())?;
|
||||
self.services.server.check_running()?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
fn validate_url(&self, url: &Url) -> Result<()> {
|
||||
if let Some(url_host) = url.host_str() {
|
||||
@@ -229,90 +248,27 @@ fn handle_error(
|
||||
Err(e.into())
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
fn sign_request(&self, http_request: &mut http::Request<Vec<u8>>, dest: &ServerName) {
|
||||
type Member = (String, Value);
|
||||
type Value = CanonicalJsonValue;
|
||||
type Object = CanonicalJsonObject;
|
||||
|
||||
let origin = &self.services.server.name;
|
||||
let body = http_request.body();
|
||||
let uri = http_request
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.expect("http::Request missing path_and_query");
|
||||
|
||||
let mut req: Object = if !body.is_empty() {
|
||||
let content: CanonicalJsonValue =
|
||||
serde_json::from_slice(body).expect("failed to serialize body");
|
||||
|
||||
let authorization: [Member; 5] = [
|
||||
("content".into(), content),
|
||||
("destination".into(), dest.as_str().into()),
|
||||
("method".into(), http_request.method().as_str().into()),
|
||||
("origin".into(), origin.as_str().into()),
|
||||
("uri".into(), uri.to_string().into()),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
} else {
|
||||
let authorization: [Member; 4] = [
|
||||
("destination".into(), dest.as_str().into()),
|
||||
("method".into(), http_request.method().as_str().into()),
|
||||
("origin".into(), origin.as_str().into()),
|
||||
("uri".into(), uri.to_string().into()),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
};
|
||||
|
||||
self.services
|
||||
.server_keys
|
||||
.sign_json(&mut req)
|
||||
.expect("request signing failed");
|
||||
|
||||
let signatures = req["signatures"]
|
||||
.as_object()
|
||||
.and_then(|object| object[origin.as_str()].as_object())
|
||||
.expect("origin signatures object");
|
||||
|
||||
let key: &ServerSigningKeyId = signatures
|
||||
.keys()
|
||||
.next()
|
||||
.map(|k| k.as_str().try_into())
|
||||
.expect("at least one signature from this origin")
|
||||
.expect("keyid is json string");
|
||||
|
||||
let sig: Base64 = signatures
|
||||
.values()
|
||||
.next()
|
||||
.map(|s| s.as_str().map(Base64::parse))
|
||||
.expect("at least one signature from this origin")
|
||||
.expect("signature is json string")
|
||||
.expect("signature is valid base64");
|
||||
|
||||
let x_matrix = XMatrix::new(origin.into(), dest.into(), key.into(), sig);
|
||||
let authorization = HeaderValue::from(&x_matrix);
|
||||
let authorization = http_request
|
||||
.headers_mut()
|
||||
.insert(AUTHORIZATION, authorization);
|
||||
|
||||
debug_assert!(authorization.is_none(), "Authorization header already present");
|
||||
/// A trait for the input types of acceptable path builders for outgoing federation requests.
|
||||
///
|
||||
/// Ruma uses Rust's type system to encode the versioning scheme of endpoints in the Matrix spec.
|
||||
/// Every endpoint has a `PathBuilder` associated type, which has an `Input` associated type.
|
||||
/// Endpoints with multiple versions have `VersionHistory` as their `PathBuilder`, which has `SupportedVersions`
|
||||
/// as its `Input` type. Endpoints with no version have `SinglePath` as their `PathBuilder`, which has `()` as its `Input` type.
|
||||
/// Both `SupportedVersions` and `()` can be created out of thin air using static data (or no data at all). This property
|
||||
/// is what the `FederationPathBuilderInput` trait represents.
|
||||
///
|
||||
/// This trait allows the federation sender service's functions to accept requests for either versioned or unversioned endpoints,
|
||||
/// by requiring that the `Input` of the `PathBuilder` of the endpoint implements `FederationPathBuilderInput`.
|
||||
pub(crate) trait FederationPathBuilderInput {
|
||||
fn create() -> Self;
|
||||
}
|
||||
|
||||
fn into_http_request<T>(actual: &ActualDest, request: T) -> Result<http::Request<Vec<u8>>>
|
||||
where
|
||||
T: OutgoingRequest + Send,
|
||||
{
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11];
|
||||
|
||||
let http_request = request
|
||||
.try_into_http_request::<Vec<u8>>(
|
||||
actual.string().as_str(),
|
||||
SendAccessToken::None,
|
||||
&VERSIONS,
|
||||
)
|
||||
.map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))?;
|
||||
|
||||
Ok(http_request)
|
||||
impl FederationPathBuilderInput for () {
|
||||
fn create() -> Self {}
|
||||
}
|
||||
|
||||
impl FederationPathBuilderInput for Cow<'_, SupportedVersions> {
|
||||
fn create() -> Self {
|
||||
Cow::Borrowed(&SUPPORTED_VERSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod execute;
|
||||
pub(crate) use execute::FederationPathBuilderInput;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
|
||||
let (ber_count, ber_bytes) = self.bad_event_ratelimiter.read().iter().fold(
|
||||
(0_usize, 0_usize),
|
||||
|(mut count, mut bytes), (event_id, _)| {
|
||||
bytes = bytes.saturating_add(event_id.capacity());
|
||||
bytes = bytes.saturating_add(event_id.as_bytes().len());
|
||||
bytes = bytes.saturating_add(size_of::<RateLimitState>());
|
||||
count = count.saturating_add(1);
|
||||
(count, bytes)
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
use database::{Deserialized, Ignore, Interfix, Json, Map};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
OwnedRoomId, RoomId, UserId,
|
||||
api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
|
||||
serde::Raw,
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup}, serde::Raw
|
||||
};
|
||||
|
||||
use crate::{Dep, globals};
|
||||
@@ -103,7 +101,7 @@ pub async fn update_backup<'a>(
|
||||
|
||||
#[implement(Service)]
|
||||
pub async fn get_latest_backup_version(&self, user_id: &UserId) -> Result<String> {
|
||||
type Key<'a> = (&'a UserId, &'a str);
|
||||
type Key<'a> = (OwnedUserId, &'a str);
|
||||
|
||||
let last_possible_key = (user_id, u64::MAX);
|
||||
self.db
|
||||
@@ -122,7 +120,7 @@ pub async fn get_latest_backup(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<(String, Raw<BackupAlgorithm>)> {
|
||||
type Key<'a> = (&'a UserId, &'a str);
|
||||
type Key<'a> = (OwnedUserId, &'a str);
|
||||
type KeyVal<'a> = (Key<'a>, Raw<BackupAlgorithm>);
|
||||
|
||||
let last_possible_key = (user_id, u64::MAX);
|
||||
@@ -197,11 +195,11 @@ pub async fn get_all(
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
) -> BTreeMap<OwnedRoomId, RoomKeyBackup> {
|
||||
type Key<'a> = (Ignore, Ignore, &'a RoomId, &'a str);
|
||||
type Key<'a> = (Ignore, Ignore, OwnedRoomId, &'a str);
|
||||
type KeyVal<'a> = (Key<'a>, Raw<KeyBackupData>);
|
||||
|
||||
let mut rooms = BTreeMap::<OwnedRoomId, RoomKeyBackup>::new();
|
||||
let default = || RoomKeyBackup { sessions: BTreeMap::new() };
|
||||
let default = || RoomKeyBackup::new(BTreeMap::new());
|
||||
|
||||
let prefix = (user_id, version, Interfix);
|
||||
self.db
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
};
|
||||
use database::{Database, Interfix, Map};
|
||||
use futures::StreamExt;
|
||||
use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition};
|
||||
use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition};
|
||||
|
||||
use crate::media::mxc::Mxc;
|
||||
|
||||
use super::{preview::UrlPreviewData, thumbnail::Dim};
|
||||
|
||||
@@ -41,7 +43,7 @@ pub(super) fn create_file_metadata(
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let dim: &[u32] = &[dim.width, dim.height];
|
||||
let key = (mxc, dim, content_disposition, content_type);
|
||||
let key = (mxc, dim, content_disposition.map(ToString::to_string), content_type);
|
||||
let key = database::serialize_key(key)?;
|
||||
self.mediaid_file.insert(&key, []);
|
||||
if let Some(user) = user {
|
||||
@@ -146,7 +148,7 @@ pub(super) async fn get_all_user_mxcs(&self, user_id: &UserId) -> Vec<OwnedMxcUr
|
||||
self.mediaid_user
|
||||
.stream()
|
||||
.ignore_err()
|
||||
.ready_filter_map(|(key, user): (&str, &UserId)| {
|
||||
.ready_filter_map(|(key, user): (&str, OwnedUserId)| {
|
||||
(user == user_id).then(|| key.into())
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod blurhash;
|
||||
pub mod mxc;
|
||||
mod data;
|
||||
pub(super) mod migrations;
|
||||
mod preview;
|
||||
@@ -17,7 +18,7 @@
|
||||
},
|
||||
warn,
|
||||
};
|
||||
use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition};
|
||||
use ruma::{OwnedMxcUri, UserId, http_headers::ContentDisposition};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
use self::data::{Data, Metadata};
|
||||
pub use self::thumbnail::Dim;
|
||||
use crate::{Dep, client, globals, moderation, sending};
|
||||
use crate::{Dep, client, globals, media::mxc::Mxc, moderation, sending};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileMeta {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruma::{MxcUri, MxcUriError, OwnedMxcUri, ServerName};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
/// A structured, valid MXC URI
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Mxc<'a> {
|
||||
/// ServerName part of the MXC URI
|
||||
pub server_name: &'a ServerName,
|
||||
|
||||
/// MediaId part of the MXC URI
|
||||
pub media_id: &'a str,
|
||||
}
|
||||
|
||||
impl fmt::Display for Mxc<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "mxc://{}/{}", self.server_name, self.media_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a MxcUri> for Mxc<'a> {
|
||||
type Error = MxcUriError;
|
||||
|
||||
fn try_from(s: &'a MxcUri) -> Result<Self, Self::Error> {
|
||||
let (server_name, media_id) = s.parts()?;
|
||||
|
||||
Ok(Self { server_name, media_id })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Mxc<'a> {
|
||||
type Error = MxcUriError;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||
let s: &MxcUri = s.into();
|
||||
s.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a OwnedMxcUri> for Mxc<'a> {
|
||||
type Error = MxcUriError;
|
||||
|
||||
fn try_from(s: &'a OwnedMxcUri) -> Result<Self, Self::Error> {
|
||||
let s: &MxcUri = s.as_ref();
|
||||
s.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Mxc<'_> {
|
||||
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,8 @@ pub async fn download_image(
|
||||
) -> Result<UrlPreviewData> {
|
||||
use conduwuit::utils::random_string;
|
||||
use image::ImageReader;
|
||||
use ruma::Mxc;
|
||||
|
||||
use crate::media::mxc::Mxc;
|
||||
|
||||
let mut preview_data = preview_data.unwrap_or_default();
|
||||
|
||||
|
||||
+60
-62
@@ -6,18 +6,16 @@
|
||||
};
|
||||
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
|
||||
use ruma::{
|
||||
Mxc, ServerName, UserId,
|
||||
api::{
|
||||
OutgoingRequest,
|
||||
client::{
|
||||
ServerName, UserId, api::{
|
||||
Metadata, OutgoingRequest, auth_scheme::NoAuthentication, client::{
|
||||
error::ErrorKind::{NotFound, Unrecognized},
|
||||
media,
|
||||
},
|
||||
federation,
|
||||
federation::authenticated_media::{Content, FileOrLocation},
|
||||
},
|
||||
}, federation::{self, authenticated_media::{Content, FileOrLocation}, authentication::ServerSignatures}, path_builder::PathBuilder
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc};
|
||||
|
||||
use super::{Dim, FileMeta};
|
||||
|
||||
#[implement(super::Service)]
|
||||
@@ -87,14 +85,10 @@ async fn fetch_thumbnail_authenticated(
|
||||
) -> Result<FileMeta> {
|
||||
use federation::authenticated_media::get_content_thumbnail::v1::{Request, Response};
|
||||
|
||||
let request = Request {
|
||||
media_id: mxc.media_id.into(),
|
||||
method: dim.method.clone().into(),
|
||||
width: dim.width.into(),
|
||||
height: dim.height.into(),
|
||||
animated: true.into(),
|
||||
timeout_ms,
|
||||
};
|
||||
let mut request = Request::new(mxc.media_id.into(), dim.width.into(), dim.height.into());
|
||||
request.method = Some(dim.method.clone());
|
||||
request.animated = Some(true);
|
||||
request.timeout_ms = timeout_ms;
|
||||
|
||||
let Response { content, .. } = self.federation_request(mxc, server, request).await?;
|
||||
|
||||
@@ -102,6 +96,7 @@ async fn fetch_thumbnail_authenticated(
|
||||
| FileOrLocation::File(content) =>
|
||||
self.handle_thumbnail_file(mxc, user, dim, content).await,
|
||||
| FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
| _ => Err!("Unknown content in response"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,16 +110,15 @@ async fn fetch_content_authenticated(
|
||||
) -> Result<FileMeta> {
|
||||
use federation::authenticated_media::get_content::v1::{Request, Response};
|
||||
|
||||
let request = Request {
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
};
|
||||
let mut request = Request::new(mxc.media_id.into());
|
||||
request.timeout_ms = timeout_ms;
|
||||
|
||||
let Response { content, .. } = self.federation_request(mxc, server, request).await?;
|
||||
|
||||
match content {
|
||||
| FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await,
|
||||
| FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
| _ => Err!("Unknown content in response"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,23 +134,18 @@ async fn fetch_thumbnail_unauthenticated(
|
||||
) -> Result<FileMeta> {
|
||||
use media::get_content_thumbnail::v3::{Request, Response};
|
||||
|
||||
let request = Request {
|
||||
allow_remote: true,
|
||||
allow_redirect: true,
|
||||
animated: true.into(),
|
||||
method: dim.method.clone().into(),
|
||||
width: dim.width.into(),
|
||||
height: dim.height.into(),
|
||||
server_name: mxc.server_name.into(),
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
};
|
||||
let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into(), dim.width.into(), dim.height.into());
|
||||
request.allow_redirect = true;
|
||||
request.allow_remote = true;
|
||||
request.animated = Some(true);
|
||||
request.method = Some(dim.method.clone());
|
||||
request.timeout_ms = timeout_ms;
|
||||
|
||||
let Response {
|
||||
file, content_type, content_disposition, ..
|
||||
} = self.federation_request(mxc, server, request).await?;
|
||||
} = self.federation_request_unauthenticated(mxc, server, request).await?;
|
||||
|
||||
let content = Content { file, content_type, content_disposition };
|
||||
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
|
||||
|
||||
self.handle_thumbnail_file(mxc, user, dim, content).await
|
||||
}
|
||||
@@ -172,19 +161,16 @@ async fn fetch_content_unauthenticated(
|
||||
) -> Result<FileMeta> {
|
||||
use media::get_content::v3::{Request, Response};
|
||||
|
||||
let request = Request {
|
||||
allow_remote: true,
|
||||
allow_redirect: true,
|
||||
server_name: mxc.server_name.into(),
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
};
|
||||
let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into());
|
||||
request.allow_remote = true;
|
||||
request.allow_redirect = true;
|
||||
request.timeout_ms = timeout_ms;
|
||||
|
||||
let Response {
|
||||
file, content_type, content_disposition, ..
|
||||
} = self.federation_request(mxc, server, request).await?;
|
||||
} = self.federation_request_unauthenticated(mxc, server, request).await?;
|
||||
|
||||
let content = Content { file, content_type, content_disposition };
|
||||
let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap());
|
||||
|
||||
self.handle_content_file(mxc, user, content).await
|
||||
}
|
||||
@@ -307,14 +293,14 @@ async fn location_request(&self, location: &str) -> Result<FileMeta> {
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn federation_request<Request>(
|
||||
async fn federation_request<'i, Request>(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
server: Option<&ServerName>,
|
||||
request: Request,
|
||||
) -> Result<Request::IncomingResponse>
|
||||
where
|
||||
Request: OutgoingRequest + Send + Debug,
|
||||
Request: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
|
||||
{
|
||||
self.services
|
||||
.sending
|
||||
@@ -322,6 +308,22 @@ async fn federation_request<Request>(
|
||||
.await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn federation_request_unauthenticated<'i, Request>(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
server: Option<&ServerName>,
|
||||
request: Request,
|
||||
) -> Result<Request::IncomingResponse>
|
||||
where
|
||||
Request: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
|
||||
{
|
||||
self.services
|
||||
.sending
|
||||
.send_unauthenticated_request(server.unwrap_or(mxc.server_name), request)
|
||||
.await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
#[allow(deprecated)]
|
||||
pub async fn fetch_remote_thumbnail_legacy(
|
||||
@@ -333,22 +335,19 @@ pub async fn fetch_remote_thumbnail_legacy(
|
||||
media_id: &body.media_id,
|
||||
};
|
||||
|
||||
let mut request = media::get_content_thumbnail::v3::Request::new(body.media_id.clone(), body.server_name.clone(), body.width, body.height);
|
||||
request.method = body.method.clone();
|
||||
request.allow_remote = body.allow_remote;
|
||||
request.allow_redirect = body.allow_redirect;
|
||||
request.animated = body.animated;
|
||||
request.timeout_ms = body.timeout_ms;
|
||||
|
||||
self.check_legacy_freeze()?;
|
||||
self.check_fetch_authorized(&mxc)?;
|
||||
let response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(mxc.server_name, media::get_content_thumbnail::v3::Request {
|
||||
allow_remote: body.allow_remote,
|
||||
height: body.height,
|
||||
width: body.width,
|
||||
method: body.method.clone(),
|
||||
server_name: body.server_name.clone(),
|
||||
media_id: body.media_id.clone(),
|
||||
timeout_ms: body.timeout_ms,
|
||||
allow_redirect: body.allow_redirect,
|
||||
animated: body.animated,
|
||||
})
|
||||
.send_unauthenticated_request(mxc.server_name, request)
|
||||
.await?;
|
||||
|
||||
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
|
||||
@@ -373,18 +372,17 @@ pub async fn fetch_remote_content_legacy(
|
||||
allow_redirect: bool,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<media::get_content::v3::Response, Error> {
|
||||
let mut request = media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into());
|
||||
request.allow_remote = true;
|
||||
request.allow_redirect = allow_redirect;
|
||||
request.timeout_ms = timeout_ms;
|
||||
|
||||
self.check_legacy_freeze()?;
|
||||
self.check_fetch_authorized(mxc)?;
|
||||
let response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(mxc.server_name, media::get_content::v3::Request {
|
||||
allow_remote: true,
|
||||
server_name: mxc.server_name.into(),
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
allow_redirect,
|
||||
})
|
||||
.send_unauthenticated_request(mxc.server_name, request)
|
||||
.await?;
|
||||
|
||||
let content_disposition = make_content_disposition(
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
use std::{cmp, num::Saturating as Sat};
|
||||
|
||||
use conduwuit::{Result, checked, err, implement};
|
||||
use ruma::{Mxc, UInt, UserId, http_headers::ContentDisposition, media::Method};
|
||||
use ruma::{UInt, UserId, http_headers::ContentDisposition, media::Method};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
|
||||
use crate::media::mxc::Mxc;
|
||||
|
||||
use super::{FileMeta, data::Metadata};
|
||||
|
||||
/// Dimension specification for a thumbnail.
|
||||
|
||||
+23
-25
@@ -243,7 +243,13 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
services
|
||||
.users
|
||||
.stream()
|
||||
.filter(|user_id| services.users.is_active_local(user_id))
|
||||
.filter_map(async |user_id| {
|
||||
if services.users.is_active_local(&user_id).await {
|
||||
Some(user_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ready_for_each(|user_id| {
|
||||
let matches = patterns.matches(user_id.localpart());
|
||||
if matches.matched_any() {
|
||||
@@ -268,7 +274,6 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
.rooms
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
{
|
||||
@@ -305,7 +310,6 @@ async fn db_lt_12(services: &Services) -> Result<()> {
|
||||
for username in &services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
@@ -385,7 +389,6 @@ async fn db_lt_13(services: &Services) -> Result<()> {
|
||||
for username in &services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
@@ -480,7 +483,6 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services)
|
||||
.rooms
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
@@ -491,7 +493,6 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services)
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
@@ -603,11 +604,8 @@ async fn fix_referencedevents_missing_sep(services: &Services) -> Result {
|
||||
}
|
||||
|
||||
async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result {
|
||||
use conduwuit::arrayvec::ArrayString;
|
||||
use ruma::identifiers_validation::MAX_BYTES;
|
||||
|
||||
type ArrayId = ArrayString<MAX_BYTES>;
|
||||
type Key<'a> = (&'a RoomId, u64, &'a UserId);
|
||||
type ArrayId = String;
|
||||
type Key = (OwnedRoomId, u64, OwnedUserId);
|
||||
|
||||
info!("Fixing undeleted entries in readreceiptid_readreceipt...");
|
||||
|
||||
@@ -621,8 +619,8 @@ async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result
|
||||
readreceiptid_readreceipt
|
||||
.keys()
|
||||
.expect_ok()
|
||||
.ready_for_each(|key: Key<'_>| {
|
||||
let (room_id, _, user_id) = key;
|
||||
.ready_for_each(|key: Key| {
|
||||
let (ref room_id, _, ref user_id) = key;
|
||||
let last_room = cur_room.replace(
|
||||
room_id
|
||||
.as_str()
|
||||
@@ -715,8 +713,8 @@ async fn fix_corrupt_msc4133_fields(services: &Services) -> Result {
|
||||
|
||||
const POPULATED_USERROOMID_LEFTSTATE_TABLE_MARKER: &str = "populate_userroomid_leftstate_table";
|
||||
async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
|
||||
type KeyVal<'a> = (Key<'a>, Raw<Option<Pdu>>);
|
||||
type Key<'a> = (&'a UserId, &'a RoomId);
|
||||
type KeyVal = (Key, Raw<Option<Pdu>>);
|
||||
type Key = (OwnedUserId, OwnedRoomId);
|
||||
|
||||
let db = &services.db;
|
||||
let cork = db.cork_and_sync();
|
||||
@@ -731,16 +729,16 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
|
||||
usize,
|
||||
HashMap<_, _>,
|
||||
),
|
||||
((user_id, room_id), state): KeyVal<'_>|
|
||||
((user_id, room_id), state): KeyVal|
|
||||
-> Result<(usize, usize, HashMap<_, _>)> {
|
||||
if state.deserialize().is_err() {
|
||||
let latest_shortstatehash =
|
||||
if let Some(shortstatehash) = shortstatehash_cache.get(room_id) {
|
||||
if let Some(shortstatehash) = shortstatehash_cache.get(&room_id) {
|
||||
*shortstatehash
|
||||
} else if let Ok(shortstatehash) =
|
||||
services.rooms.state.get_room_shortstatehash(room_id).await
|
||||
services.rooms.state.get_room_shortstatehash(&room_id).await
|
||||
{
|
||||
shortstatehash_cache.insert(room_id.to_owned(), shortstatehash);
|
||||
shortstatehash_cache.insert(room_id.clone(), shortstatehash);
|
||||
shortstatehash
|
||||
} else {
|
||||
warn!(%room_id, %user_id, "room has no shortstatehash");
|
||||
@@ -792,8 +790,8 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
|
||||
async fn fix_local_invite_state(services: &Services) -> Result {
|
||||
// Clean up the effects of !1249 by caching stripped state for invites
|
||||
|
||||
type KeyVal<'a> = (Key<'a>, Raw<Vec<AnyStrippedStateEvent>>);
|
||||
type Key<'a> = (&'a UserId, &'a RoomId);
|
||||
type KeyVal = (Key, Raw<Vec<AnyStrippedStateEvent>>);
|
||||
type Key = (OwnedUserId, OwnedRoomId);
|
||||
|
||||
let db = &services.db;
|
||||
let cork = db.cork_and_sync();
|
||||
@@ -802,9 +800,9 @@ async fn fix_local_invite_state(services: &Services) -> Result {
|
||||
// for each user invited to a room
|
||||
let fixed = userroomid_invitestate.stream()
|
||||
// if they're a local user on this homeserver
|
||||
.try_filter(|((user_id, _), _): &KeyVal<'_>| ready(services.globals.user_is_local(user_id)))
|
||||
.and_then(async |((user_id, room_id), stripped_state): KeyVal<'_>| Ok::<_,
|
||||
conduwuit::Error>((user_id.to_owned(), room_id.to_owned(), stripped_state.deserialize
|
||||
.try_filter(|((user_id, _), _): &KeyVal| ready(services.globals.user_is_local(user_id)))
|
||||
.and_then(async |((user_id, room_id), stripped_state): KeyVal| Ok::<_,
|
||||
conduwuit::Error>((user_id.clone(), room_id.clone(), stripped_state.deserialize
|
||||
().unwrap_or_else(|e| {
|
||||
trace!("Failed to deserialize: {:?}", stripped_state.json());
|
||||
warn!(
|
||||
@@ -812,7 +810,7 @@ async fn fix_local_invite_state(services: &Services) -> Result {
|
||||
%room_id,
|
||||
"Failed to deserialize stripped state for invite, removing from db: {e}"
|
||||
);
|
||||
userroomid_invitestate.del((user_id, room_id));
|
||||
userroomid_invitestate.del((&user_id, &room_id));
|
||||
vec![]
|
||||
}))))
|
||||
.try_fold(0_usize, async |mut fixed, (user_id, room_id, stripped_state)| {
|
||||
|
||||
@@ -47,3 +47,11 @@
|
||||
|
||||
conduwuit::mod_ctor! {}
|
||||
conduwuit::mod_dtor! {}
|
||||
|
||||
use std::sync::LazyLock;
|
||||
use conduwuit::matrix::versions::{unstable_features, versions};
|
||||
use ruma::api::SupportedVersions;
|
||||
|
||||
pub static SUPPORTED_VERSIONS: LazyLock<SupportedVersions> = LazyLock::new(|| {
|
||||
SupportedVersions::from_parts(&versions(), &unstable_features())
|
||||
});
|
||||
@@ -183,7 +183,6 @@ pub async fn unset_all_presence(&self) {
|
||||
.services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
@@ -194,9 +193,9 @@ pub async fn unset_all_presence(&self) {
|
||||
| _ => continue,
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
if matches!(
|
||||
presence.presence,
|
||||
PresenceState::Unavailable | PresenceState::Online | PresenceState::Busy
|
||||
PresenceState::Offline
|
||||
) {
|
||||
trace!(%user_id, ?presence, "Skipping user");
|
||||
continue;
|
||||
|
||||
@@ -47,17 +47,16 @@ pub(super) async fn to_presence_event(
|
||||
) -> PresenceEvent {
|
||||
let now = utils::millis_since_unix_epoch();
|
||||
let last_active_ago = Some(UInt::new_saturating(now.saturating_sub(self.last_active_ts)));
|
||||
let mut content = PresenceEventContent::new(self.state.clone());
|
||||
content.status_msg = self.status_msg.clone();
|
||||
content.currently_active = Some(self.currently_active);
|
||||
content.last_active_ago = last_active_ago;
|
||||
content.displayname = users.displayname(user_id).await.ok();
|
||||
content.avatar_url = users.avatar_url(user_id).await.ok();
|
||||
|
||||
PresenceEvent {
|
||||
sender: user_id.to_owned(),
|
||||
content: PresenceEventContent {
|
||||
presence: self.state.clone(),
|
||||
status_msg: self.status_msg.clone(),
|
||||
currently_active: Some(self.currently_active),
|
||||
last_active_ago,
|
||||
displayname: users.displayname(user_id).await.ok(),
|
||||
avatar_url: users.avatar_url(user_id).await.ok(),
|
||||
},
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-39
@@ -11,24 +11,17 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use ipaddress::IPAddress;
|
||||
use ruma::{
|
||||
DeviceId, OwnedDeviceId, RoomId, UInt, UserId,
|
||||
api::{
|
||||
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||
client::push::{Pusher, PusherKind, set_pusher},
|
||||
push_gateway::send_event_notification::{
|
||||
DeviceId, OwnedDeviceId, RoomId, UInt, UserId, api::{
|
||||
IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{NoAuthentication, SendAccessToken}, client::push::{Pusher, PusherKind, set_pusher}, path_builder::SinglePath, push_gateway::send_event_notification::{
|
||||
self,
|
||||
v1::{Device, Notification, NotificationCounts, NotificationPriority},
|
||||
},
|
||||
},
|
||||
events::{
|
||||
}
|
||||
}, events::{
|
||||
AnySyncTimelineEvent, StateEventType, TimelineEventType,
|
||||
room::power_levels::RoomPowerLevelsEventContent,
|
||||
},
|
||||
push::{
|
||||
room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}},
|
||||
}, push::{
|
||||
Action, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak,
|
||||
},
|
||||
serde::Raw,
|
||||
uint,
|
||||
}, room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules}, serde::Raw, uint
|
||||
};
|
||||
|
||||
use crate::{Dep, client, config, globals, rooms, sending, users};
|
||||
@@ -42,6 +35,7 @@ struct Services {
|
||||
globals: Dep<globals::Service>,
|
||||
config: Dep<config::Service>,
|
||||
client: Dep<client::Service>,
|
||||
state: Dep<rooms::state::Service>,
|
||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||
state_cache: Dep<rooms::state_cache::Service>,
|
||||
users: Dep<users::Service>,
|
||||
@@ -64,6 +58,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
client: args.depend::<client::Service>("client"),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
state: args.depend::<rooms::state::Service>("rooms::state"),
|
||||
state_accessor: args
|
||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
||||
@@ -137,6 +132,7 @@ pub async fn set_pusher(
|
||||
| set_pusher::v3::PusherAction::Delete(ids) => {
|
||||
self.delete_pusher(sender, ids.pushkey.as_str()).await;
|
||||
},
|
||||
| _ => return Err!(Request(InvalidParam("Unknown pusher action"))),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -193,7 +189,7 @@ pub fn get_pushkeys<'a>(
|
||||
#[tracing::instrument(skip(self, dest, request))]
|
||||
pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath> + Debug + Send,
|
||||
{
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0];
|
||||
|
||||
@@ -201,7 +197,7 @@ pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::Incomin
|
||||
trace!("Push gateway destination: {dest}");
|
||||
|
||||
let http_request = request
|
||||
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, &VERSIONS)
|
||||
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, ())
|
||||
.map_err(|e| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Failed to find destination {dest} for push gateway: {e}"
|
||||
@@ -298,22 +294,20 @@ pub async fn send_push_notice<E>(
|
||||
{
|
||||
let mut notify = None;
|
||||
let mut tweaks = Vec::new();
|
||||
if event.room_id().is_none() {
|
||||
// This only affects v12+ create events
|
||||
let Some(room_id) = event.room_id() else {
|
||||
// Only v12+ create events have no room ID
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let power_levels: RoomPowerLevelsEventContent = self
|
||||
let power_levels = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get(event.room_id().unwrap(), &StateEventType::RoomPowerLevels, "")
|
||||
.await
|
||||
.and_then(|event| event.get_content())
|
||||
.unwrap_or_default();
|
||||
.get_room_power_levels(room_id)
|
||||
.await;
|
||||
|
||||
let serialized = event.to_format();
|
||||
for action in self
|
||||
.get_actions(user, &ruleset, &power_levels, &serialized, event.room_id().unwrap())
|
||||
.get_actions(user, &ruleset, power_levels.clone(), &serialized, event.room_id().unwrap())
|
||||
.await
|
||||
{
|
||||
let n = match action {
|
||||
@@ -347,15 +341,11 @@ pub async fn get_actions<'a>(
|
||||
&self,
|
||||
user: &UserId,
|
||||
ruleset: &'a Ruleset,
|
||||
power_levels: &RoomPowerLevelsEventContent,
|
||||
power_levels: RoomPowerLevels,
|
||||
pdu: &Raw<AnySyncTimelineEvent>,
|
||||
room_id: &RoomId,
|
||||
) -> &'a [Action] {
|
||||
let power_levels = PushConditionPowerLevelsCtx {
|
||||
users: power_levels.users.clone(),
|
||||
users_default: power_levels.users_default,
|
||||
notifications: power_levels.notifications.clone(),
|
||||
};
|
||||
let power_levels = PushConditionPowerLevelsCtx::from(power_levels);
|
||||
|
||||
let room_joined_count = self
|
||||
.services
|
||||
@@ -373,15 +363,9 @@ pub async fn get_actions<'a>(
|
||||
.await
|
||||
.unwrap_or_else(|_| user.localpart().to_owned());
|
||||
|
||||
let ctx = PushConditionRoomCtx {
|
||||
room_id: room_id.to_owned(),
|
||||
member_count: room_joined_count,
|
||||
user_id: user.to_owned(),
|
||||
user_display_name,
|
||||
power_levels: Some(power_levels),
|
||||
};
|
||||
let ctx = PushConditionRoomCtx::new(room_id.to_owned(), room_joined_count, user.to_owned(), user_display_name).with_power_levels(power_levels);
|
||||
|
||||
ruleset.get_actions(pdu, &ctx)
|
||||
ruleset.get_actions(pdu, &ctx).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, unread, pusher, tweaks, event))]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
};
|
||||
use database::{Cbor, Deserialized, Map};
|
||||
use futures::{Stream, StreamExt, future::join};
|
||||
use ruma::ServerName;
|
||||
use ruma::{OwnedServerName, ServerName};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::fed::FedDest;
|
||||
@@ -107,19 +107,19 @@ pub async fn get_override(&self, name: &str) -> Result<CachedOverride> {
|
||||
}
|
||||
|
||||
#[implement(Cache)]
|
||||
pub fn destinations(&self) -> impl Stream<Item = (&ServerName, CachedDest)> + Send + '_ {
|
||||
pub fn destinations(&self) -> impl Stream<Item = (OwnedServerName, CachedDest)> + Send + '_ {
|
||||
self.destinations
|
||||
.stream()
|
||||
.ignore_err()
|
||||
.map(|item: (&ServerName, Cbor<_>)| (item.0, item.1.0))
|
||||
.map(|item: (OwnedServerName, Cbor<_>)| (item.0, item.1.0))
|
||||
}
|
||||
|
||||
#[implement(Cache)]
|
||||
pub fn overrides(&self) -> impl Stream<Item = (&ServerName, CachedOverride)> + Send + '_ {
|
||||
pub fn overrides(&self) -> impl Stream<Item = (OwnedServerName, CachedOverride)> + Send + '_ {
|
||||
self.overrides
|
||||
.stream()
|
||||
.ignore_err()
|
||||
.map(|item: (&ServerName, Cbor<_>)| (item.0, item.1.0))
|
||||
.map(|item: (OwnedServerName, Cbor<_>)| (item.0, item.1.0))
|
||||
}
|
||||
|
||||
impl CachedDest {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use conduwuit::{
|
||||
Result, debug, debug_error, debug_info, implement, trace, utils::response::LimitReadExt,
|
||||
};
|
||||
use ruma::{OwnedServerName, ServerName};
|
||||
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))]
|
||||
@@ -40,7 +43,7 @@ pub(super) async fn request_well_known(&self, dest: &str) -> Result<Option<Strin
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
if ruma::identifiers_validation::server_name::validate(m_server).is_err() {
|
||||
if ServerName::parse(m_server).is_err() {
|
||||
debug_error!("response content missing or invalid");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
use database::{Deserialized, Ignore, Interfix, Map};
|
||||
use futures::{Stream, StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId,
|
||||
events::{
|
||||
OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId, events::{
|
||||
StateEventType,
|
||||
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending};
|
||||
@@ -179,7 +178,6 @@ pub async fn resolve_alias(
|
||||
.services
|
||||
.state_cache
|
||||
.room_servers(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
return Ok((room_id, servers));
|
||||
@@ -197,22 +195,22 @@ pub async fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<OwnedRoom
|
||||
pub fn local_aliases_for_room<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &'a RoomAliasId> + Send + 'a {
|
||||
) -> impl Stream<Item = OwnedRoomAliasId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.aliasid_alias
|
||||
.stream_prefix(&prefix)
|
||||
.ignore_err()
|
||||
.map(|(_, alias): (Ignore, &RoomAliasId)| alias)
|
||||
.map(|(_, alias): (Ignore, OwnedRoomAliasId)| alias)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self), level = "debug")]
|
||||
pub fn all_local_aliases(&self) -> impl Stream<Item = (&RoomId, &str)> + Send + '_ {
|
||||
pub fn all_local_aliases(&self) -> impl Stream<Item = (OwnedRoomId, &str)> + Send + '_ {
|
||||
self.db
|
||||
.alias_roomid
|
||||
.stream()
|
||||
.ignore_err()
|
||||
.map(|(alias_localpart, room_id): (&str, &RoomId)| (room_id, alias_localpart))
|
||||
.map(|(alias_localpart, room_id): (&str, OwnedRoomId)| (room_id, alias_localpart))
|
||||
}
|
||||
|
||||
async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<bool> {
|
||||
@@ -236,34 +234,14 @@ async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) ->
|
||||
}
|
||||
|
||||
// Checking whether the user is able to change canonical aliases of the room
|
||||
if let Ok(power_levels) = self
|
||||
let can_change_canonical_alias = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
&room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.map_ok(RoomPowerLevels::from)
|
||||
.get_room_power_levels(&room_id)
|
||||
.await
|
||||
{
|
||||
return Ok(
|
||||
power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
|
||||
);
|
||||
}
|
||||
.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias);
|
||||
|
||||
// If there is no power levels event, only the room creator can change
|
||||
// canonical aliases
|
||||
if let Ok(event) = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
{
|
||||
return Ok(event.sender() == user_id);
|
||||
}
|
||||
|
||||
Err!(Database("Room has no m.room.create event"))
|
||||
Ok(can_change_canonical_alias)
|
||||
}
|
||||
|
||||
async fn who_created_alias(&self, alias: &RoomAliasId) -> Result<OwnedUserId> {
|
||||
@@ -299,7 +277,7 @@ async fn resolve_appservice_alias(
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
appservice.registration.clone(),
|
||||
query_room_alias::v1::Request { room_alias: room_alias.to_owned() },
|
||||
query_room_alias::v1::Request::new(room_alias.to_owned()),
|
||||
)
|
||||
.await,
|
||||
Ok(Some(_opt_result))
|
||||
|
||||
@@ -16,7 +16,7 @@ pub(super) async fn remote_resolve(
|
||||
error!("Unable to resolve remote room alias {}: {e}", room_alias);
|
||||
Err(e)
|
||||
},
|
||||
| Ok(Response { room_id, servers }) => {
|
||||
| Ok(Response { room_id, servers, .. }) => {
|
||||
debug!("Remote resolved {room_alias:?} to {room_id:?} with servers {servers:?}");
|
||||
Ok((room_id, servers))
|
||||
},
|
||||
@@ -31,7 +31,7 @@ async fn remote_request(
|
||||
) -> Result<Response> {
|
||||
use federation::query::get_room_information::v1::Request;
|
||||
|
||||
let request = Request { room_alias: room_alias.to_owned() };
|
||||
let request = Request::new(room_alias.to_owned());
|
||||
|
||||
self.services
|
||||
.sending
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use conduwuit::{Result, implement, utils::stream::TryIgnore};
|
||||
use database::Map;
|
||||
use futures::Stream;
|
||||
use ruma::{RoomId, api::client::room::Visibility};
|
||||
use ruma::{OwnedRoomId, RoomId, api::client::room::Visibility};
|
||||
|
||||
pub struct Service {
|
||||
db: Data,
|
||||
@@ -32,7 +32,7 @@ fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
pub fn set_not_public(&self, room_id: &RoomId) { self.db.publicroomids.remove(room_id); }
|
||||
|
||||
#[implement(Service)]
|
||||
pub fn public_rooms(&self) -> impl Stream<Item = &RoomId> + Send {
|
||||
pub fn public_rooms(&self) -> impl Stream<Item = OwnedRoomId> + Send {
|
||||
self.db.publicroomids.keys().ignore_err()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
api::federation::event::get_event,
|
||||
};
|
||||
|
||||
use super::get_room_version_id;
|
||||
use super::get_room_version;
|
||||
|
||||
/// Find the event and auth it. Once the event is validated (steps 1 - 8)
|
||||
/// it is appended to the outliers Tree.
|
||||
@@ -109,15 +109,12 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(origin, get_event::v1::Request {
|
||||
event_id: (*next_id).to_owned(),
|
||||
include_unredacted_content: None,
|
||||
})
|
||||
.send_federation_request(origin, get_event::v1::Request::new((*next_id).to_owned()))
|
||||
.await
|
||||
{
|
||||
| Ok(res) => {
|
||||
debug!("Got {next_id} over federation from {origin}");
|
||||
let Ok(room_version_id) = get_room_version_id(create_event) else {
|
||||
let Ok(room_version_id) = get_room_version(create_event) else {
|
||||
back_off((*next_id).to_owned());
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -31,10 +31,7 @@ pub(super) async fn fetch_state<Pdu>(
|
||||
let res = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(origin, get_room_state_ids::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.to_owned(),
|
||||
})
|
||||
.send_federation_request(origin, get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned()))
|
||||
.await
|
||||
.inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?;
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn should_rescind_invite(
|
||||
// Does the target user have a pending invite?
|
||||
let Ok(pending_invite_state) = services
|
||||
.state_cache
|
||||
.invite_state(target_user_id, room_id)
|
||||
.invite_state(&target_user_id, room_id)
|
||||
.await
|
||||
else {
|
||||
return Ok(None); // No pending invite, so nothing to rescind
|
||||
@@ -146,9 +146,11 @@ pub async fn handle_incoming_pdu<'a>(
|
||||
let origin_acl_check = self.acl_check(origin, room_id);
|
||||
|
||||
// 1.3.2 Check room ACL on sender's server name
|
||||
let sender: &UserId = value
|
||||
let sender: OwnedUserId = value
|
||||
.get("sender")
|
||||
.try_into()
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| err!("No sender in object"))
|
||||
.and_then(|v| Ok(UserId::parse(v)?))
|
||||
.map_err(|e| err!(Request(InvalidParam("PDU does not have a valid sender key: {e}"))))?;
|
||||
|
||||
let sender_acl_check: OptionFuture<_> = sender
|
||||
@@ -180,7 +182,7 @@ pub async fn handle_incoming_pdu<'a>(
|
||||
// copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300
|
||||
if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") {
|
||||
if let Some(pdu) =
|
||||
should_rescind_invite(&self.services, &mut value.clone(), sender, room_id).await?
|
||||
should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id).await?
|
||||
{
|
||||
debug_info!(
|
||||
"Invite to {room_id} appears to have been rescinded by {sender}, marking as \
|
||||
@@ -188,7 +190,7 @@ pub async fn handle_incoming_pdu<'a>(
|
||||
);
|
||||
self.services
|
||||
.state_cache
|
||||
.mark_as_left(sender, room_id, Some(pdu))
|
||||
.mark_as_left(&sender, room_id, Some(pdu))
|
||||
.await;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user